From 0178b88495a7a595e4790be2c2a38e95367829a1 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 26 May 2025 11:52:00 -0700 Subject: [PATCH 01/97] impl get_network_to_prune --- pallets/subtensor/src/subnets/subnet.rs | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index b122bfa049..bf7ada34d8 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -440,4 +440,40 @@ impl Pallet { pub fn is_valid_subnet_for_emission(netuid: u16) -> bool { FirstEmissionBlockNumber::::get(netuid).is_some() } + + 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 { + if FirstEmissionBlockNumber::::get(netuid).is_none() { + continue; + } + + let registered_at = NetworkRegisteredAt::::get(netuid); + let immunity_period = ImmunityPeriod::::get(netuid); + if current_block < registered_at.saturating_add(immunity_period as u64) { + 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 + } } From 562823c73e7403196a3a799a045cb97fbf91c005 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 27 May 2025 11:54:17 -0700 Subject: [PATCH 02/97] use NetworkImmunityPeriod --- pallets/subtensor/src/subnets/subnet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index bf7ada34d8..1f6524e2b2 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -455,7 +455,7 @@ impl Pallet { } let registered_at = NetworkRegisteredAt::::get(netuid); - let immunity_period = ImmunityPeriod::::get(netuid); + let immunity_period = Self::get_network_immunity_period(); if current_block < registered_at.saturating_add(immunity_period as u64) { continue; } From b2ef90e55e8290e13a8ba6ac2462a96bf0326910 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 27 May 2025 13:45:56 -0700 Subject: [PATCH 03/97] update get_network_to_prune. --- pallets/subtensor/src/subnets/subnet.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 1f6524e2b2..a04ae1df07 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -441,6 +441,11 @@ impl Pallet { FirstEmissionBlockNumber::::get(netuid).is_some() } + /// Select a subnet to prune: + /// - Only consider subnets that are Enabled. + /// - Exclude subnets still within `ImmunityPeriod`. + /// - Pick the one with the lowest total emission + /// - In the case of a tie, pick the earliest registered. pub fn get_network_to_prune() -> Option { let current_block: u64 = Self::get_current_block_as_u64(); let total_networks: u16 = TotalNetworks::::get(); @@ -450,19 +455,22 @@ impl Pallet { let mut candidate_timestamp = u64::MAX; for netuid in 1..=total_networks { - if FirstEmissionBlockNumber::::get(netuid).is_none() { - continue; - } + // Exclude disabled subnets + let first_emission_block = match FirstEmissionBlockNumber::::get(netuid) { + Some(block) => block, + None => continue, + }; - let registered_at = NetworkRegisteredAt::::get(netuid); + // Check if the subnet's immunity period is expired. let immunity_period = Self::get_network_immunity_period(); - if current_block < registered_at.saturating_add(immunity_period as u64) { + if current_block < first_emission_block.saturating_add(immunity_period as u64) { continue; } // We want total emission across all UIDs in this subnet: let emission_vec = Emission::::get(netuid); let total_emission = emission_vec.iter().sum::(); + let registered_at = NetworkRegisteredAt::::get(netuid); // If tie on total_emission, earliest registration wins if total_emission < candidate_emission From d569309fdd0a65d1dfafc0d3fbaa88bac370fca5 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 27 May 2025 14:39:41 -0700 Subject: [PATCH 04/97] add `NetworkActivationDeadline ` --- pallets/subtensor/src/coinbase/root.rs | 3 +++ pallets/subtensor/src/lib.rs | 9 +++++++++ pallets/subtensor/src/subnets/subnet.rs | 19 ++++++++++++++----- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 1f3a91b339..e8d529309d 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -627,6 +627,9 @@ impl Pallet { pub fn get_network_immunity_period() -> u64 { NetworkImmunityPeriod::::get() } + pub fn get_network_activation_deadline() -> u64 { + NetworkActivationDeadline::::get() + } pub fn set_network_immunity_period(net_immunity_period: u64) { NetworkImmunityPeriod::::set(net_immunity_period); Self::deposit_event(Event::NetworkImmunityPeriodSet(net_immunity_period)); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 197cd5f8f7..8e148bee69 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -556,6 +556,11 @@ pub mod pallet { T::InitialNetworkImmunityPeriod::get() } #[pallet::type_value] + /// Default value for network activation deadline. + pub fn DefaultNetworkActivationDeadline() -> u64 { + 1_296_000 + } + #[pallet::type_value] /// Default value for network last registered. pub fn DefaultNetworkLastRegistered() -> u64 { 0 @@ -1194,6 +1199,10 @@ pub mod pallet { pub type NetworkImmunityPeriod = StorageValue<_, u64, ValueQuery, DefaultNetworkImmunityPeriod>; #[pallet::storage] + /// ITEM( network_activation_deadline ) + pub type NetworkActivationDeadline = + StorageValue<_, u64, ValueQuery, DefaultNetworkActivationDeadline>; + #[pallet::storage] /// ITEM( network_last_registered_block ) pub type NetworkLastRegistered = StorageValue<_, u64, ValueQuery, DefaultNetworkLastRegistered>; diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index a04ae1df07..c60689d23d 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -455,22 +455,31 @@ impl Pallet { let mut candidate_timestamp = u64::MAX; for netuid in 1..=total_networks { - // Exclude disabled subnets - let first_emission_block = match FirstEmissionBlockNumber::::get(netuid) { + let registered_at = NetworkRegisteredAt::::get(netuid); + + let start_block = match FirstEmissionBlockNumber::::get(netuid) { Some(block) => block, - None => continue, + None => { + // Not enabled yet. If still within ActivationDeadline, skip pruning this subnet. + if current_block + < registered_at.saturating_add(Self::get_network_activation_deadline()) + { + continue; + } + // Otherwise, we treat it as if it started at its registered time + registered_at + } }; // Check if the subnet's immunity period is expired. let immunity_period = Self::get_network_immunity_period(); - if current_block < first_emission_block.saturating_add(immunity_period as u64) { + if current_block < start_block.saturating_add(immunity_period as u64) { continue; } // We want total emission across all UIDs in this subnet: let emission_vec = Emission::::get(netuid); let total_emission = emission_vec.iter().sum::(); - let registered_at = NetworkRegisteredAt::::get(netuid); // If tie on total_emission, earliest registration wins if total_emission < candidate_emission From 8016183fbdbafb44bcbb31f5416e372e2a191ed2 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 2 Jun 2025 08:35:21 -0700 Subject: [PATCH 05/97] WIP --- pallets/subtensor/src/coinbase/root.rs | 219 +++++++++++++++------ pallets/subtensor/src/macros/dispatches.rs | 2 +- pallets/subtensor/src/subnets/subnet.rs | 53 ----- pallets/subtensor/src/tests/networks.rs | 3 +- 4 files changed, 162 insertions(+), 115 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index e8d529309d..218137b8eb 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -22,7 +22,9 @@ use frame_support::weights::Weight; use safe_math::*; use sp_core::Get; use sp_std::vec; -use substrate_fixed::types::I64F64; +use sp_runtime::Perbill; +use substrate_fixed::types::{I64F64, U96F32}; +use sp_runtime::PerThing; impl Pallet { /// Fetches the total count of root network validators @@ -427,64 +429,25 @@ 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. - 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)); - } + 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)?; - // --- 5. Explicitly erase the network and all its parameters. + // --- Finally, remove the network entirely. + ensure!(Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist); 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 +470,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 various network-related parameters and data. Rank::::remove(netuid); Trust::::remove(netuid); Active::::remove(netuid); @@ -558,16 +519,26 @@ impl Pallet { POWRegistrationsThisInterval::::remove(netuid); BurnRegistrationsThisInterval::::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); + // --- 12. Remove additional dTao-related storages if applicable. + SubnetTAO::::remove(netuid); + SubnetAlphaInEmission::::remove(netuid); + SubnetAlphaOutEmission::::remove(netuid); + SubnetTaoInEmission::::remove(netuid); + SubnetVolume::::remove(netuid); + SubnetMovingPrice::::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)] @@ -674,4 +645,134 @@ 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); + + let start_block = match FirstEmissionBlockNumber::::get(netuid) { + Some(block) => block, + None => { + // Not enabled yet. If still within ActivationDeadline, skip pruning this subnet. + if current_block + < registered_at.saturating_add(Self::get_network_activation_deadline()) + { + continue; + } + // Otherwise, we treat it as if it started at its registered time + registered_at + } + }; + + // Check if the subnet's immunity period is expired. + let immunity_period = Self::get_network_immunity_period(); + if current_block < start_block.saturating_add(immunity_period as u64) { + 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/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index c60689d23d..b122bfa049 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -440,57 +440,4 @@ impl Pallet { pub fn is_valid_subnet_for_emission(netuid: u16) -> bool { FirstEmissionBlockNumber::::get(netuid).is_some() } - - /// Select a subnet to prune: - /// - Only consider subnets that are Enabled. - /// - Exclude subnets still within `ImmunityPeriod`. - /// - Pick the one with the lowest total emission - /// - In the case of a tie, pick the earliest registered. - 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); - - let start_block = match FirstEmissionBlockNumber::::get(netuid) { - Some(block) => block, - None => { - // Not enabled yet. If still within ActivationDeadline, skip pruning this subnet. - if current_block - < registered_at.saturating_add(Self::get_network_activation_deadline()) - { - continue; - } - // Otherwise, we treat it as if it started at its registered time - registered_at - } - }; - - // Check if the subnet's immunity period is expired. - let immunity_period = Self::get_network_immunity_period(); - if current_block < start_block.saturating_add(immunity_period as u64) { - 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/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 7dda0502c1..0a157c3a5c 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -32,8 +32,7 @@ fn test_registration_ok() { coldkey_account_id )); - assert_ok!(SubtensorModule::user_remove_network( - coldkey_account_id, + assert_ok!(SubtensorModule::do_dissolve_network( netuid )); From 000c3d0a1b420a52ac1af555d76d203ad83a4933 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 2 Jun 2025 11:08:03 -0700 Subject: [PATCH 06/97] remove NetworkActivationDeadline --- pallets/subtensor/src/coinbase/root.rs | 81 ++++++++++--------------- pallets/subtensor/src/lib.rs | 9 --- pallets/subtensor/src/tests/networks.rs | 4 +- 3 files changed, 33 insertions(+), 61 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 218137b8eb..dd8206e281 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -21,10 +21,10 @@ use frame_support::storage::IterableStorageDoubleMap; use frame_support::weights::Weight; use safe_math::*; use sp_core::Get; -use sp_std::vec; +use sp_runtime::PerThing; use sp_runtime::Perbill; +use sp_std::vec; use substrate_fixed::types::{I64F64, U96F32}; -use sp_runtime::PerThing; impl Pallet { /// Fetches the total count of root network validators @@ -434,7 +434,10 @@ impl Pallet { Self::destroy_alpha_in_out_stakes(netuid)?; // --- Finally, remove the network entirely. - ensure!(Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist); + ensure!( + Self::if_subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); Self::remove_network(netuid); // --- Emit event. @@ -489,7 +492,7 @@ impl Pallet { Weights::::insert(Self::get_root_netuid(), uid_i, modified_weights); } - // --- 10. Remove various network-related parameters and data. + // --- 10. Remove network-related parameters and data. Rank::::remove(netuid); Trust::::remove(netuid); Active::::remove(netuid); @@ -505,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); @@ -518,8 +519,6 @@ impl Pallet { RegistrationsThisInterval::::remove(netuid); POWRegistrationsThisInterval::::remove(netuid); BurnRegistrationsThisInterval::::remove(netuid); - - // --- 12. Remove additional dTao-related storages if applicable. SubnetTAO::::remove(netuid); SubnetAlphaInEmission::::remove(netuid); SubnetAlphaOutEmission::::remove(netuid); @@ -527,7 +526,6 @@ impl Pallet { SubnetVolume::::remove(netuid); SubnetMovingPrice::::remove(netuid); - // --- 13. Remove subnet identity if it exists. if SubnetIdentitiesV2::::contains_key(netuid) { SubnetIdentitiesV2::::remove(netuid); Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); @@ -598,9 +596,6 @@ impl Pallet { pub fn get_network_immunity_period() -> u64 { NetworkImmunityPeriod::::get() } - pub fn get_network_activation_deadline() -> u64 { - NetworkActivationDeadline::::get() - } pub fn set_network_immunity_period(net_immunity_period: u64) { NetworkImmunityPeriod::::set(net_immunity_period); Self::deposit_event(Event::NetworkImmunityPeriodSet(net_immunity_period)); @@ -648,26 +643,29 @@ impl Pallet { fn destroy_alpha_in_out_stakes(netuid: u16) -> DispatchResult { // 1. Ensure the subnet exists. - ensure!(Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist); - + 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 { @@ -677,54 +675,54 @@ impl Pallet { 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(()) } @@ -739,23 +737,8 @@ impl Pallet { for netuid in 1..=total_networks { let registered_at = NetworkRegisteredAt::::get(netuid); - let start_block = match FirstEmissionBlockNumber::::get(netuid) { - Some(block) => block, - None => { - // Not enabled yet. If still within ActivationDeadline, skip pruning this subnet. - if current_block - < registered_at.saturating_add(Self::get_network_activation_deadline()) - { - continue; - } - // Otherwise, we treat it as if it started at its registered time - registered_at - } - }; - - // Check if the subnet's immunity period is expired. - let immunity_period = Self::get_network_immunity_period(); - if current_block < start_block.saturating_add(immunity_period as u64) { + // Skip immune networks + if current_block < registered_at.saturating_add(Self::get_network_immunity_period()) { continue; } @@ -774,5 +757,5 @@ impl Pallet { } candidate_netuid - } + } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 8e148bee69..197cd5f8f7 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -556,11 +556,6 @@ pub mod pallet { T::InitialNetworkImmunityPeriod::get() } #[pallet::type_value] - /// Default value for network activation deadline. - pub fn DefaultNetworkActivationDeadline() -> u64 { - 1_296_000 - } - #[pallet::type_value] /// Default value for network last registered. pub fn DefaultNetworkLastRegistered() -> u64 { 0 @@ -1199,10 +1194,6 @@ pub mod pallet { pub type NetworkImmunityPeriod = StorageValue<_, u64, ValueQuery, DefaultNetworkImmunityPeriod>; #[pallet::storage] - /// ITEM( network_activation_deadline ) - pub type NetworkActivationDeadline = - StorageValue<_, u64, ValueQuery, DefaultNetworkActivationDeadline>; - #[pallet::storage] /// ITEM( network_last_registered_block ) pub type NetworkLastRegistered = StorageValue<_, u64, ValueQuery, DefaultNetworkLastRegistered>; diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 0a157c3a5c..c9bb787437 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -32,9 +32,7 @@ fn test_registration_ok() { coldkey_account_id )); - assert_ok!(SubtensorModule::do_dissolve_network( - netuid - )); + assert_ok!(SubtensorModule::do_dissolve_network(netuid)); assert!(!SubtensorModule::if_subnet_exist(netuid)) }) From dfe72d5cbfaee7d6684197865434c45f5503b527 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 2 Jun 2025 11:18:37 -0700 Subject: [PATCH 07/97] update InitialNetworkImmunityPeriod --- pallets/admin-utils/src/tests/mock.rs | 2 +- pallets/subtensor/src/tests/mock.rs | 2 +- runtime/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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/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 From 2676c6326361d9007d43ec880509a067f6ddfd37 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 5 Jun 2025 12:50:54 -0700 Subject: [PATCH 08/97] add dissolve_network tests --- pallets/subtensor/src/tests/networks.rs | 293 +++++++++++++++++++++++- 1 file changed, 292 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index c9bb787437..57bc32c713 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -1,8 +1,9 @@ use super::mock::*; use crate::*; -use frame_support::assert_ok; +use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; +use substrate_fixed::types::U64F64; #[test] fn test_registration_ok() { @@ -38,6 +39,296 @@ fn test_registration_ok() { }) } +#[test] +fn dissolve_no_stakers_no_alpha_no_emission() { + new_test_ext(0).execute_with(|| { + let cold = U256::from(1); + let hot = U256::from(2); + let net = add_dynamic_network(&hot, &cold); + + SubtensorModule::set_subnet_locked_balance(net, 0); + SubnetTAO::::insert(net, 0); + Emission::::insert(net, Vec::::new()); + + let before = SubtensorModule::get_coldkey_balance(&cold); + assert_ok!(SubtensorModule::do_dissolve_network(net)); + let after = SubtensorModule::get_coldkey_balance(&cold); + + // Balance should be unchanged (whatever the network-lock bookkeeping left there) + assert_eq!(after, before); + assert!(!SubtensorModule::if_subnet_exist(net)); + }); +} + +#[test] +fn dissolve_refunds_full_lock_cost_when_no_emission() { + new_test_ext(0).execute_with(|| { + let cold = U256::from(3); + let hot = U256::from(4); + let net = add_dynamic_network(&hot, &cold); + + let lock = 1_000_000u64; + SubtensorModule::set_subnet_locked_balance(net, lock); + SubnetTAO::::insert(net, 0); + Emission::::insert(net, Vec::::new()); + + let before = SubtensorModule::get_coldkey_balance(&cold); + assert_ok!(SubtensorModule::do_dissolve_network(net)); + let after = SubtensorModule::get_coldkey_balance(&cold); + + assert_eq!(after, before + lock); + }); +} + +#[test] +fn dissolve_single_alpha_out_staker_gets_all_tao() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(10); + let owner_hot = U256::from(20); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + let s_hot = U256::from(100); + let s_cold = U256::from(200); + + Alpha::::insert((s_hot, s_cold, net), U64F64::from_num(5_000u128)); + SubnetTAO::::insert(net, 99_999); + SubtensorModule::set_subnet_locked_balance(net, 0); + Emission::::insert(net, Vec::::new()); + + let before = SubtensorModule::get_coldkey_balance(&s_cold); + assert_ok!(SubtensorModule::do_dissolve_network(net)); + let after = SubtensorModule::get_coldkey_balance(&s_cold); + + assert_eq!(after, before + 99_999); + assert!(Alpha::::iter().count() == 0); + }); +} + +#[test] +fn dissolve_two_stakers_pro_rata_distribution() { + new_test_ext(0).execute_with(|| { + let oc = U256::from(50); + let oh = U256::from(51); + let net = add_dynamic_network(&oh, &oc); + + // stakers α-out + let (s1_hot, s1_cold, a1) = (U256::from(201), U256::from(301), 300u128); + let (s2_hot, s2_cold, a2) = (U256::from(202), U256::from(302), 700u128); + + Alpha::::insert((s1_hot, s1_cold, net), U64F64::from_num(a1)); + Alpha::::insert((s2_hot, s2_cold, net), U64F64::from_num(a2)); + + SubnetTAO::::insert(net, 10_000); + SubtensorModule::set_subnet_locked_balance(net, 5_000); + Emission::::insert(net, Vec::::new()); + + let b1 = SubtensorModule::get_coldkey_balance(&s1_cold); + let b2 = SubtensorModule::get_coldkey_balance(&s2_cold); + let bo = SubtensorModule::get_coldkey_balance(&oc); + + assert_ok!(SubtensorModule::do_dissolve_network(net)); + + let total = a1 + a2; + let share1: u64 = (10_000u128 * a1 / total) as u64; + let share2: u64 = (10_000u128 * a2 / total) as u64; + + assert_eq!(SubtensorModule::get_coldkey_balance(&s1_cold), b1 + share1); + assert_eq!(SubtensorModule::get_coldkey_balance(&s2_cold), b2 + share2); + assert_eq!(SubtensorModule::get_coldkey_balance(&oc), bo + 5_000); + }); +} + +#[test] +fn dissolve_owner_cut_refund_logic() { + new_test_ext(0).execute_with(|| { + let oc = U256::from(70); + let oh = U256::from(71); + let net = add_dynamic_network(&oh, &oc); + + // staker + let sh = U256::from(77); + let sc = U256::from(88); + Alpha::::insert((sh, sc, net), U64F64::from_num(100u128)); + SubnetTAO::::insert(net, 1_000); + + // lock & emission + let lock = 2_000; + SubtensorModule::set_subnet_locked_balance(net, lock); + Emission::::insert(net, vec![200u64, 600]); + + // 18 % owner-cut + SubnetOwnerCut::::put(11_796u16); + let frac = 11_796f64 / 65_535f64; + let owner_em = (800f64 * frac).floor() as u64; + let expect = lock.saturating_sub(owner_em); + + let before = SubtensorModule::get_coldkey_balance(&oc); + assert_ok!(SubtensorModule::do_dissolve_network(net)); + let after = SubtensorModule::get_coldkey_balance(&oc); + + assert_eq!(after, before + expect); + }); +} + +#[test] +fn dissolve_zero_refund_when_emission_exceeds_lock() { + new_test_ext(0).execute_with(|| { + let oc = U256::from(1_000); + let oh = U256::from(2_000); + let net = add_dynamic_network(&oh, &oc); + + SubtensorModule::set_subnet_locked_balance(net, 1_000); + SubnetOwnerCut::::put(u16::MAX); // 100 % + Emission::::insert(net, vec![2_000u64]); + + let before = SubtensorModule::get_coldkey_balance(&oc); + assert_ok!(SubtensorModule::do_dissolve_network(net)); + let after = SubtensorModule::get_coldkey_balance(&oc); + + assert_eq!(after, before); // no refund + }); +} + +#[test] +fn dissolve_nonexistent_subnet_fails() { + new_test_ext(0).execute_with(|| { + assert_err!( + SubtensorModule::do_dissolve_network(9_999), + Error::::SubNetworkDoesNotExist + ); + }); +} + +#[test] +fn dissolve_clears_all_per_subnet_storages() { + new_test_ext(0).execute_with(|| { + + let owner_cold = U256::from(123); + let owner_hot = U256::from(456); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + // ------------------------------------------------------------------ + // Populate each storage item with a minimal value of the CORRECT type + // ------------------------------------------------------------------ + SubnetOwner::::insert(net, owner_cold); + SubnetworkN::::insert(net, 0u16); + NetworkModality::::insert(net, 0u16); + NetworksAdded::::insert(net, true); + NetworkRegisteredAt::::insert(net, 0u64); + + Rank::::insert(net, vec![1u16]); + Trust::::insert(net, vec![1u16]); + Active::::insert(net, vec![true]); + Emission::::insert(net, vec![1u64]); + Incentive::::insert(net, vec![1u16]); + Consensus::::insert(net, vec![1u16]); + Dividends::::insert(net, vec![1u16]); + PruningScores::::insert(net, vec![1u16]); + LastUpdate::::insert(net, vec![0u64]); + + ValidatorPermit::::insert(net, vec![true]); + ValidatorTrust::::insert(net, vec![1u16]); + + Tempo::::insert(net, 1u16); + Kappa::::insert(net, 1u16); + Difficulty::::insert(net, 1u64); + + MaxAllowedUids::::insert(net, 1u16); + ImmunityPeriod::::insert(net, 1u16); + ActivityCutoff::::insert(net, 1u16); + MaxWeightsLimit::::insert(net, 1u16); + MinAllowedWeights::::insert(net, 1u16); + + RegistrationsThisInterval::::insert(net, 1u16); + POWRegistrationsThisInterval::::insert(net, 1u16); + BurnRegistrationsThisInterval::::insert(net, 1u16); + + SubnetTAO::::insert(net, 1u64); + SubnetAlphaInEmission::::insert(net, 1u64); + SubnetAlphaOutEmission::::insert(net, 1u64); + SubnetTaoInEmission::::insert(net, 1u64); + SubnetVolume::::insert(net, 1u128); + + // Fields that will be ZEROED (not removed) + SubnetAlphaIn::::insert(net, 2u64); + SubnetAlphaOut::::insert(net, 3u64); + + // Prefix / double-map collections + Keys::::insert(net, 0u16, owner_hot); + Bonds::::insert(net, 0u16, vec![(0u16, 1u16)]); + Weights::::insert(net, 0u16, vec![(1u16, 1u16)]); + IsNetworkMember::::insert(owner_cold, net, true); + + // ------------------------------------------------------------------ + // Dissolve + // ------------------------------------------------------------------ + assert_ok!(SubtensorModule::do_dissolve_network(net)); + + // ------------------------------------------------------------------ + // Items that must be COMPLETELY REMOVED + // ------------------------------------------------------------------ + assert!(!SubnetOwner::::contains_key(net)); + assert!(!SubnetworkN::::contains_key(net)); + assert!(!NetworkModality::::contains_key(net)); + assert!(!NetworksAdded::::contains_key(net)); + assert!(!NetworkRegisteredAt::::contains_key(net)); + + assert!(!Rank::::contains_key(net)); + assert!(!Trust::::contains_key(net)); + assert!(!Active::::contains_key(net)); + assert!(!Emission::::contains_key(net)); + assert!(!Incentive::::contains_key(net)); + assert!(!Consensus::::contains_key(net)); + assert!(!Dividends::::contains_key(net)); + assert!(!PruningScores::::contains_key(net)); + assert!(!LastUpdate::::contains_key(net)); + + assert!(!ValidatorPermit::::contains_key(net)); + assert!(!ValidatorTrust::::contains_key(net)); + + assert!(!Tempo::::contains_key(net)); + assert!(!Kappa::::contains_key(net)); + assert!(!Difficulty::::contains_key(net)); + + assert!(!MaxAllowedUids::::contains_key(net)); + assert!(!ImmunityPeriod::::contains_key(net)); + assert!(!ActivityCutoff::::contains_key(net)); + assert!(!MaxWeightsLimit::::contains_key(net)); + assert!(!MinAllowedWeights::::contains_key(net)); + + assert!(!RegistrationsThisInterval::::contains_key(net)); + assert!(!POWRegistrationsThisInterval::::contains_key(net)); + assert!(!BurnRegistrationsThisInterval::::contains_key(net)); + + assert!(!SubnetTAO::::contains_key(net)); + assert!(!SubnetAlphaInEmission::::contains_key(net)); + assert!(!SubnetAlphaOutEmission::::contains_key(net)); + assert!(!SubnetTaoInEmission::::contains_key(net)); + assert!(!SubnetVolume::::contains_key(net)); + + // ------------------------------------------------------------------ + // Items expected to be PRESENT but ZERO + // ------------------------------------------------------------------ + assert_eq!(SubnetAlphaIn::::get(net), 0); + assert_eq!(SubnetAlphaOut::::get(net), 0); + + // ------------------------------------------------------------------ + // Collections fully cleared + // ------------------------------------------------------------------ + assert!(Keys::::iter_prefix(net).next().is_none()); + assert!(Bonds::::iter_prefix(net).next().is_none()); + assert!(Weights::::iter_prefix(net).next().is_none()); + assert!(!IsNetworkMember::::contains_key(owner_hot, net)); + + // ------------------------------------------------------------------ + // Final subnet removal confirmation + // ------------------------------------------------------------------ + assert!(!SubtensorModule::if_subnet_exist(net)); + }); +} + + + // #[test] // fn test_schedule_dissolve_network_execution() { // new_test_ext(1).execute_with(|| { From a4c78f3bf3196f4191875b387ff28c3d44f53843 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 5 Jun 2025 12:51:10 -0700 Subject: [PATCH 09/97] fmt --- pallets/subtensor/src/tests/networks.rs | 41 ++++++++++++------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 57bc32c713..67ee34437e 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -43,8 +43,8 @@ fn test_registration_ok() { fn dissolve_no_stakers_no_alpha_no_emission() { new_test_ext(0).execute_with(|| { let cold = U256::from(1); - let hot = U256::from(2); - let net = add_dynamic_network(&hot, &cold); + let hot = U256::from(2); + let net = add_dynamic_network(&hot, &cold); SubtensorModule::set_subnet_locked_balance(net, 0); SubnetTAO::::insert(net, 0); @@ -52,7 +52,7 @@ fn dissolve_no_stakers_no_alpha_no_emission() { let before = SubtensorModule::get_coldkey_balance(&cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); - let after = SubtensorModule::get_coldkey_balance(&cold); + let after = SubtensorModule::get_coldkey_balance(&cold); // Balance should be unchanged (whatever the network-lock bookkeeping left there) assert_eq!(after, before); @@ -64,8 +64,8 @@ fn dissolve_no_stakers_no_alpha_no_emission() { fn dissolve_refunds_full_lock_cost_when_no_emission() { new_test_ext(0).execute_with(|| { let cold = U256::from(3); - let hot = U256::from(4); - let net = add_dynamic_network(&hot, &cold); + let hot = U256::from(4); + let net = add_dynamic_network(&hot, &cold); let lock = 1_000_000u64; SubtensorModule::set_subnet_locked_balance(net, lock); @@ -74,7 +74,7 @@ fn dissolve_refunds_full_lock_cost_when_no_emission() { let before = SubtensorModule::get_coldkey_balance(&cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); - let after = SubtensorModule::get_coldkey_balance(&cold); + let after = SubtensorModule::get_coldkey_balance(&cold); assert_eq!(after, before + lock); }); @@ -84,10 +84,10 @@ fn dissolve_refunds_full_lock_cost_when_no_emission() { fn dissolve_single_alpha_out_staker_gets_all_tao() { new_test_ext(0).execute_with(|| { let owner_cold = U256::from(10); - let owner_hot = U256::from(20); - let net = add_dynamic_network(&owner_hot, &owner_cold); + let owner_hot = U256::from(20); + let net = add_dynamic_network(&owner_hot, &owner_cold); - let s_hot = U256::from(100); + let s_hot = U256::from(100); let s_cold = U256::from(200); Alpha::::insert((s_hot, s_cold, net), U64F64::from_num(5_000u128)); @@ -97,7 +97,7 @@ fn dissolve_single_alpha_out_staker_gets_all_tao() { let before = SubtensorModule::get_coldkey_balance(&s_cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); - let after = SubtensorModule::get_coldkey_balance(&s_cold); + let after = SubtensorModule::get_coldkey_balance(&s_cold); assert_eq!(after, before + 99_999); assert!(Alpha::::iter().count() == 0); @@ -134,7 +134,7 @@ fn dissolve_two_stakers_pro_rata_distribution() { assert_eq!(SubtensorModule::get_coldkey_balance(&s1_cold), b1 + share1); assert_eq!(SubtensorModule::get_coldkey_balance(&s2_cold), b2 + share2); - assert_eq!(SubtensorModule::get_coldkey_balance(&oc), bo + 5_000); + assert_eq!(SubtensorModule::get_coldkey_balance(&oc), bo + 5_000); }); } @@ -146,8 +146,8 @@ fn dissolve_owner_cut_refund_logic() { let net = add_dynamic_network(&oh, &oc); // staker - let sh = U256::from(77); - let sc = U256::from(88); + let sh = U256::from(77); + let sc = U256::from(88); Alpha::::insert((sh, sc, net), U64F64::from_num(100u128)); SubnetTAO::::insert(net, 1_000); @@ -158,13 +158,13 @@ fn dissolve_owner_cut_refund_logic() { // 18 % owner-cut SubnetOwnerCut::::put(11_796u16); - let frac = 11_796f64 / 65_535f64; + let frac = 11_796f64 / 65_535f64; let owner_em = (800f64 * frac).floor() as u64; - let expect = lock.saturating_sub(owner_em); + let expect = lock.saturating_sub(owner_em); let before = SubtensorModule::get_coldkey_balance(&oc); assert_ok!(SubtensorModule::do_dissolve_network(net)); - let after = SubtensorModule::get_coldkey_balance(&oc); + let after = SubtensorModule::get_coldkey_balance(&oc); assert_eq!(after, before + expect); }); @@ -183,7 +183,7 @@ fn dissolve_zero_refund_when_emission_exceeds_lock() { let before = SubtensorModule::get_coldkey_balance(&oc); assert_ok!(SubtensorModule::do_dissolve_network(net)); - let after = SubtensorModule::get_coldkey_balance(&oc); + let after = SubtensorModule::get_coldkey_balance(&oc); assert_eq!(after, before); // no refund }); @@ -202,10 +202,9 @@ fn dissolve_nonexistent_subnet_fails() { #[test] fn dissolve_clears_all_per_subnet_storages() { new_test_ext(0).execute_with(|| { - let owner_cold = U256::from(123); - let owner_hot = U256::from(456); - let net = add_dynamic_network(&owner_hot, &owner_cold); + let owner_hot = U256::from(456); + let net = add_dynamic_network(&owner_hot, &owner_cold); // ------------------------------------------------------------------ // Populate each storage item with a minimal value of the CORRECT type @@ -327,8 +326,6 @@ fn dissolve_clears_all_per_subnet_storages() { }); } - - // #[test] // fn test_schedule_dissolve_network_execution() { // new_test_ext(1).execute_with(|| { From ac29e34c401b329b6bc5319c141b8a350db3e2fc Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 06:36:53 -0700 Subject: [PATCH 10/97] unused param --- pallets/subtensor/src/macros/dispatches.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 0a0d9083f5..d3db94c727 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1227,7 +1227,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] pub fn dissolve_network( origin: OriginFor, - coldkey: T::AccountId, + _coldkey: T::AccountId, netuid: u16, ) -> DispatchResult { ensure_root(origin)?; From 17f098210dad02fa1e5cd5d36edb022d20f5c5d2 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 06:37:25 -0700 Subject: [PATCH 11/97] clean up destroy_alpha_in_out_stakes --- pallets/subtensor/src/coinbase/root.rs | 76 ++++++++++---------------- 1 file changed, 30 insertions(+), 46 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index dd8206e281..2a6dedeb75 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -21,8 +21,6 @@ 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, U96F32}; @@ -641,86 +639,72 @@ impl Pallet { LastRateLimitedBlock::::set(rate_limit_key, block); } - fn destroy_alpha_in_out_stakes(netuid: u16) -> DispatchResult { + pub 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. + // 2. Gather basic 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). + // How much Tao the subnet has emitted and what the owner already earned. + let total_emission: u64 = Emission::::get(netuid).iter().sum(); 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). + // 3. Collect α-out staker data. let mut total_alpha_out: u128 = 0; - let mut stakers_data = Vec::new(); + let mut stakers = 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)); + let amount = alpha_shares.saturating_to_num::(); + total_alpha_out = total_alpha_out.saturating_add(amount); + stakers.push((hotkey, coldkey, amount)); } } - // (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); + // 4. Distribute the subnet’s Tao pro-rata. + let subnet_tao_u128 = SubnetTAO::::get(netuid) as u128; - for (hotkey, coldkey, alpha_amount) in stakers_data { - let scaled = alpha_amount - .saturating_mul(accuracy_as_u128) + if total_alpha_out > 0 && subnet_tao_u128 > 0 { + for (hotkey, coldkey, alpha_amount) in &stakers { + // tao_share = subnet_tao * α / Σα + let share_u128 = subnet_tao_u128 + .saturating_mul(*alpha_amount) .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); + let share_u64 = share_u128.min(u64::MAX as u128) as u64; - // 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); + if share_u64 > 0 { + Self::add_balance_to_coldkey_account(coldkey, share_u64); + } - // Remove these alpha shares. + Alpha::::remove((hotkey.clone(), coldkey.clone(), netuid)); + } + } else { + // No α-out stakers: just clear any lingering records. + for (hotkey, coldkey, _) in &stakers { Alpha::::remove((hotkey.clone(), coldkey.clone(), netuid)); } } - // Clear any leftover alpha in/out accumulations. + // 5. Reset α 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. + // 6. Refund any remaining lock (lock_cost − owner_cut already paid out). + let refund = lock_cost.saturating_sub(owner_received_emission); Self::set_subnet_locked_balance(netuid, 0); - if final_refund > 0 { - Self::add_balance_to_coldkey_account(&owner_coldkey, final_refund); + if refund > 0 { + Self::add_balance_to_coldkey_account(&owner_coldkey, refund); } Ok(()) From 4b03bc9730aa51a37a00f0070deec901b67bef27 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:26:31 -0700 Subject: [PATCH 12/97] update destroy_alpha_in_out_stakes --- pallets/subtensor/src/coinbase/root.rs | 83 ++++++++++++++++++-------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 2a6dedeb75..61cec45c9f 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -639,6 +639,14 @@ impl Pallet { LastRateLimitedBlock::::set(rate_limit_key, block); } + /// Burns **nothing**: every Tao in `SubnetTAO` is now paid out to + /// α-out stakers, including any units that would previously have been + /// lost to flooring. + /// + /// Rounding strategy + /// 1. First pass – give each staker `floor(T * α / Σα)` Tao. + /// 2. Second pass – distribute the *left-over* ( < #stakers ) one-by-one + /// to the stakers with the largest fractional remainders. pub fn destroy_alpha_in_out_stakes(netuid: u16) -> DispatchResult { // 1. Ensure the subnet exists. ensure!( @@ -646,63 +654,86 @@ impl Pallet { Error::::SubNetworkDoesNotExist ); - // 2. Gather basic info. + // 2. Basic info. let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); let lock_cost: u64 = Self::get_subnet_locked_balance(netuid); - // How much Tao the subnet has emitted and what the owner already earned. + // Owner-cut already received from emissions. let total_emission: u64 = Emission::::get(netuid).iter().sum(); 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. Collect α-out staker data. + // 3. Gather α-out stakers. let mut total_alpha_out: u128 = 0; - let mut stakers = Vec::new(); + let mut stakers: Vec<(T::AccountId, T::AccountId, u128)> = Vec::new(); - for ((hotkey, coldkey, this_netuid), alpha_shares) in Alpha::::iter() { + for ((hot, cold, this_netuid), alpha) in Alpha::::iter() { if this_netuid == netuid { - let amount = alpha_shares.saturating_to_num::(); - total_alpha_out = total_alpha_out.saturating_add(amount); - stakers.push((hotkey, coldkey, amount)); + let a = alpha.saturating_to_num::(); + total_alpha_out = total_alpha_out.saturating_add(a); + stakers.push((hot, cold, a)); } } - // 4. Distribute the subnet’s Tao pro-rata. - let subnet_tao_u128 = SubnetTAO::::get(netuid) as u128; + // 4. Pro-rata distribution WITH remainder handling. + let subnet_tao: u128 = SubnetTAO::::get(netuid) as u128; - if total_alpha_out > 0 && subnet_tao_u128 > 0 { - for (hotkey, coldkey, alpha_amount) in &stakers { - // tao_share = subnet_tao * α / Σα - let share_u128 = subnet_tao_u128 - .saturating_mul(*alpha_amount) - .checked_div(total_alpha_out) - .unwrap_or(0); + if total_alpha_out > 0 && subnet_tao > 0 && !stakers.is_empty() { + struct Portion { + hot: A, + cold: C, + share: u64, + rem: u128, + } + let mut portions: Vec> = Vec::with_capacity(stakers.len()); + let mut distributed: u128 = 0; + for (hot, cold, a) in &stakers { + let prod = subnet_tao.saturating_mul(*a); + let share_u128 = prod / total_alpha_out; let share_u64 = share_u128.min(u64::MAX as u128) as u64; + distributed = distributed.saturating_add(share_u64 as u128); + + portions.push(Portion { + hot: hot.clone(), + cold: cold.clone(), + share: share_u64, + rem: prod % total_alpha_out, + }); + } - if share_u64 > 0 { - Self::add_balance_to_coldkey_account(coldkey, share_u64); + // Left-over units ( < stakers.len() ). + let leftover = subnet_tao.saturating_sub(distributed); + if leftover > 0 { + portions.sort_by(|a, b| b.rem.cmp(&a.rem)); + for p in portions.iter_mut().take(leftover as usize) { + p.share = p.share.saturating_add(1); } + } - Alpha::::remove((hotkey.clone(), coldkey.clone(), netuid)); + // Final crediting and α-record cleanup. + for p in portions { + if p.share > 0 { + Self::add_balance_to_coldkey_account(&p.cold, p.share); + } + Alpha::::remove((&p.hot, &p.cold, netuid)); } } else { - // No α-out stakers: just clear any lingering records. - for (hotkey, coldkey, _) in &stakers { - Alpha::::remove((hotkey.clone(), coldkey.clone(), netuid)); + // No α-out or no Tao – just clear α-records. + for (hot, cold, _) in &stakers { + Alpha::::remove((hot.clone(), cold.clone(), netuid)); } } - // 5. Reset α in/out accumulations. + // 5. Reset α-in/out accumulations. SubnetAlphaIn::::insert(netuid, 0); SubnetAlphaOut::::insert(netuid, 0); - // 6. Refund any remaining lock (lock_cost − owner_cut already paid out). + // 6. Refund remaining lock. let refund = lock_cost.saturating_sub(owner_received_emission); Self::set_subnet_locked_balance(netuid, 0); - if refund > 0 { Self::add_balance_to_coldkey_account(&owner_coldkey, refund); } From 7c205a9b67fe0a0ee63db848691d5ebe69c55a4e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:26:44 -0700 Subject: [PATCH 13/97] add more tests --- pallets/subtensor/src/tests/networks.rs | 76 +++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 67ee34437e..94e7214ec0 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -326,6 +326,82 @@ fn dissolve_clears_all_per_subnet_storages() { }); } +#[test] +fn dissolve_alpha_out_but_zero_tao_no_rewards() { + new_test_ext(0).execute_with(|| { + let oc = U256::from(21); + let oh = U256::from(22); + let net = add_dynamic_network(&oh, &oc); + + let sh = U256::from(23); + let sc = U256::from(24); + + Alpha::::insert((sh, sc, net), U64F64::from_num(1_000u128)); + SubnetTAO::::insert(net, 0u64); // zero TAO + SubtensorModule::set_subnet_locked_balance(net, 0); + Emission::::insert(net, Vec::::new()); + + let before = SubtensorModule::get_coldkey_balance(&sc); + assert_ok!(SubtensorModule::do_dissolve_network(net)); + let after = SubtensorModule::get_coldkey_balance(&sc); + + // No reward distributed, α-out cleared. + assert_eq!(after, before); + assert!(Alpha::::iter().next().is_none()); + }); +} + +#[test] +fn dissolve_decrements_total_networks() { + new_test_ext(0).execute_with(|| { + let total_before = TotalNetworks::::get(); + + let cold = U256::from(41); + let hot = U256::from(42); + let net = add_dynamic_network(&hot, &cold); + + // Sanity: adding network increments the counter. + assert_eq!(TotalNetworks::::get(), total_before + 1); + + assert_ok!(SubtensorModule::do_dissolve_network(net)); + assert_eq!(TotalNetworks::::get(), total_before); + }); +} + +#[test] +fn dissolve_rounding_remainder_distribution() { + new_test_ext(0).execute_with(|| { + let oc = U256::from(61); + let oh = U256::from(62); + let net = add_dynamic_network(&oh, &oc); + + // α-out stakes + let (s1h, s1c, a1) = (U256::from(63), U256::from(64), 3u128); + let (s2h, s2c, a2) = (U256::from(65), U256::from(66), 2u128); + + Alpha::::insert((s1h, s1c, net), U64F64::from_num(a1)); + Alpha::::insert((s2h, s2c, net), U64F64::from_num(a2)); + + // TAO pot = 1 + SubnetTAO::::insert(net, 1u64); + SubtensorModule::set_subnet_locked_balance(net, 0); + Emission::::insert(net, Vec::::new()); + + let b1 = SubtensorModule::get_coldkey_balance(&s1c); + let b2 = SubtensorModule::get_coldkey_balance(&s2c); + + assert_ok!(SubtensorModule::do_dissolve_network(net)); + + // s1 (larger remainder) receives the single Tao. + assert_eq!(SubtensorModule::get_coldkey_balance(&s1c), b1 + 1); + assert_eq!(SubtensorModule::get_coldkey_balance(&s2c), b2); + + // α-records cleared; TAO storage gone. + assert!(Alpha::::iter().next().is_none()); + assert!(!SubnetTAO::::contains_key(net)); + }); +} + // #[test] // fn test_schedule_dissolve_network_execution() { // new_test_ext(1).execute_with(|| { From f9483054e9e8dd49843040e6c7d9cd9ab1953425 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:00:12 -0700 Subject: [PATCH 14/97] add destroy_alpha_out_multiple_stakers_pro_rata --- pallets/subtensor/src/tests/networks.rs | 97 ++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 94e7214ec0..20bec813f3 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -371,8 +371,8 @@ fn dissolve_decrements_total_networks() { #[test] fn dissolve_rounding_remainder_distribution() { new_test_ext(0).execute_with(|| { - let oc = U256::from(61); - let oh = U256::from(62); + let oc = U256::from(61); + let oh = U256::from(62); let net = add_dynamic_network(&oh, &oc); // α-out stakes @@ -402,6 +402,99 @@ fn dissolve_rounding_remainder_distribution() { }); } +#[test] +fn destroy_alpha_out_multiple_stakers_pro_rata() { + new_test_ext(0).execute_with(|| { + // -------------------------------------------------- + // 1. Subnet owner + subnet creation + // -------------------------------------------------- + let owner_cold = U256::from(10); + let owner_hot = U256::from(20); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + // -------------------------------------------------- + // 2. Two stakers – register hotkeys on the subnet + // -------------------------------------------------- + let (c1, h1) = (U256::from(111), U256::from(211)); + let (c2, h2) = (U256::from(222), U256::from(333)); + register_ok_neuron(net, h1, c1, 0); + register_ok_neuron(net, h2, c2, 0); + + // -------------------------------------------------- + // 3. Discover protocol-minimum amount (stake + fee) + // -------------------------------------------------- + let min_stake_total = + DefaultMinStake::::get().saturating_add(DefaultStakingFee::::get()); + + // target α-ratio 30 : 70 + let s1 = 3 * min_stake_total; + let s2 = 7 * min_stake_total; + + // -------------------------------------------------- + // 4. Fund coldkeys sufficiently, then stake via extrinsic + // -------------------------------------------------- + SubtensorModule::add_balance_to_coldkey_account(&c1, s1 + 50_000); + SubtensorModule::add_balance_to_coldkey_account(&c2, s2 + 50_000); + + assert_ok!(SubtensorModule::do_add_stake( + RuntimeOrigin::signed(c1), + h1, + net, + s1 + )); + assert_ok!(SubtensorModule::do_add_stake( + RuntimeOrigin::signed(c2), + h2, + net, + s2 + )); + + // -------------------------------------------------- + // 5. α snapshot + // -------------------------------------------------- + let a1: u128 = Alpha::::get((h1, c1, net)).saturating_to_num(); + let a2: u128 = Alpha::::get((h2, c2, net)).saturating_to_num(); + let atotal = a1 + a2; + + // -------------------------------------------------- + // 6. TAO pot + subnet lock + // -------------------------------------------------- + let tao_pot: u64 = 10_000; + SubnetTAO::::insert(net, tao_pot); + SubtensorModule::set_subnet_locked_balance(net, 5_000); + Emission::::insert(net, Vec::::new()); // owner earned nothing + + // -------------------------------------------------- + // 7. Balances before distribution + // -------------------------------------------------- + let b1 = SubtensorModule::get_coldkey_balance(&c1); + let b2 = SubtensorModule::get_coldkey_balance(&c2); + let bo = SubtensorModule::get_coldkey_balance(&owner_cold); + + // -------------------------------------------------- + // 8. Execute payout logic + // -------------------------------------------------- + assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(net)); + + // -------------------------------------------------- + // 9. Expected shares + // -------------------------------------------------- + let share1: u64 = (tao_pot as u128 * a1 / atotal) as u64; + let share2: u64 = tao_pot - share1; + + // -------------------------------------------------- + // 10. Assertions + // -------------------------------------------------- + assert_eq!(SubtensorModule::get_coldkey_balance(&c1), b1 + share1); + assert_eq!(SubtensorModule::get_coldkey_balance(&c2), b2 + share2); + assert_eq!( + SubtensorModule::get_coldkey_balance(&owner_cold), + bo + 5_000 + ); + assert!(Alpha::::iter().next().is_none()); + }); +} + // #[test] // fn test_schedule_dissolve_network_execution() { // new_test_ext(1).execute_with(|| { From 39f77cf84d0b1f176145bb4aae6f49ce667da1ca Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:24:27 -0700 Subject: [PATCH 15/97] add test destroy_alpha_out_many_stakers_complex_distribution --- pallets/subtensor/src/tests/networks.rs | 126 +++++++++++++++++++++--- 1 file changed, 115 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 20bec813f3..ad9aea6002 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -410,15 +410,15 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { // -------------------------------------------------- let owner_cold = U256::from(10); let owner_hot = U256::from(20); - let net = add_dynamic_network(&owner_hot, &owner_cold); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); // -------------------------------------------------- // 2. Two stakers – register hotkeys on the subnet // -------------------------------------------------- let (c1, h1) = (U256::from(111), U256::from(211)); let (c2, h2) = (U256::from(222), U256::from(333)); - register_ok_neuron(net, h1, c1, 0); - register_ok_neuron(net, h2, c2, 0); + register_ok_neuron(netuid, h1, c1, 0); + register_ok_neuron(netuid, h2, c2, 0); // -------------------------------------------------- // 3. Discover protocol-minimum amount (stake + fee) @@ -439,30 +439,30 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { assert_ok!(SubtensorModule::do_add_stake( RuntimeOrigin::signed(c1), h1, - net, + netuid, s1 )); assert_ok!(SubtensorModule::do_add_stake( RuntimeOrigin::signed(c2), h2, - net, + netuid, s2 )); // -------------------------------------------------- // 5. α snapshot // -------------------------------------------------- - let a1: u128 = Alpha::::get((h1, c1, net)).saturating_to_num(); - let a2: u128 = Alpha::::get((h2, c2, net)).saturating_to_num(); + let a1: u128 = Alpha::::get((h1, c1, netuid)).saturating_to_num(); + let a2: u128 = Alpha::::get((h2, c2, netuid)).saturating_to_num(); let atotal = a1 + a2; // -------------------------------------------------- // 6. TAO pot + subnet lock // -------------------------------------------------- let tao_pot: u64 = 10_000; - SubnetTAO::::insert(net, tao_pot); - SubtensorModule::set_subnet_locked_balance(net, 5_000); - Emission::::insert(net, Vec::::new()); // owner earned nothing + SubnetTAO::::insert(netuid, tao_pot); + SubtensorModule::set_subnet_locked_balance(netuid, 5_000); + Emission::::insert(netuid, Vec::::new()); // -------------------------------------------------- // 7. Balances before distribution @@ -474,7 +474,7 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { // -------------------------------------------------- // 8. Execute payout logic // -------------------------------------------------- - assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(net)); + assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); // -------------------------------------------------- // 9. Expected shares @@ -495,6 +495,110 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { }); } +#[test] +fn destroy_alpha_out_many_stakers_complex_distribution() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(1_000); + let owner_hot = U256::from(2_000); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + SubtensorModule::set_max_registrations_per_block(netuid, 1000u16); + SubtensorModule::set_target_registrations_per_interval(netuid, 1000u16); + + let min_total = + DefaultMinStake::::get().saturating_add(DefaultStakingFee::::get()); + + const N: usize = 20; + let mut cold = [U256::zero(); N]; + let mut hot = [U256::zero(); N]; + let mut stake = [0u64; N]; + + for i in 0..N { + cold[i] = U256::from(10_000 + 2 * i as u32); + hot[i] = U256::from(10_001 + 2 * i as u32); + stake[i] = (i as u64 + 1) * min_total; + + register_ok_neuron(netuid, hot[i], cold[i], 0); + SubtensorModule::add_balance_to_coldkey_account(&cold[i], stake[i] + 100_000); + + assert_ok!(SubtensorModule::do_add_stake( + RuntimeOrigin::signed(cold[i]), + hot[i], + netuid, + stake[i] + )); + } + + let mut alpha = [0u128; N]; + let mut a_sum: u128 = 0; + for i in 0..N { + alpha[i] = Alpha::::get((hot[i], cold[i], netuid)).saturating_to_num(); + a_sum += alpha[i]; + } + + let tao_pot: u64 = 123_456; + let lock: u64 = 30_000; + + SubnetTAO::::insert(netuid, tao_pot); + SubtensorModule::set_subnet_locked_balance(netuid, lock); + + // prior emissions (owner already earned some) + Emission::::insert(netuid, vec![1_000u64, 2_000, 1_500]); + + // owner-cut = 50 % exactly + SubnetOwnerCut::::put(32_768); + + let mut before = [0u64; N]; + for i in 0..N { + before[i] = SubtensorModule::get_coldkey_balance(&cold[i]); + } + let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); + + let owner_em: u64 = (4_500u128 * 32_768u128 / 65_535u128) as u64; + let expected_refund = lock.saturating_sub(owner_em); + + // Compute expected shares per pallet algorithm + let mut share = [0u64; N]; + let mut rem = [0u128; N]; + let mut paid: u128 = 0; + + for i in 0..N { + let prod = tao_pot as u128 * alpha[i]; + share[i] = (prod / a_sum) as u64; + rem[i] = prod % a_sum; + paid += share[i] as u128; + } + let leftover = tao_pot as u128 - paid; + // distribute +1 Tao to stakers with largest remainders + let mut idx: Vec<_> = (0..N).collect(); + idx.sort_by_key(|i| std::cmp::Reverse(rem[*i])); + for i in 0..leftover as usize { + share[idx[i]] += 1; + } + + assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); + + // Assertions + for i in 0..N { + assert_eq!( + SubtensorModule::get_coldkey_balance(&cold[i]), + before[i] + share[i], + "staker {} incorrect payout", + i + 1 + ); + } + // b) owner refund is correct + assert_eq!( + SubtensorModule::get_coldkey_balance(&owner_cold), + owner_before + expected_refund + ); + // c) α cleared and counters reset + assert!(Alpha::::iter().next().is_none()); + assert_eq!(SubnetAlphaIn::::get(netuid), 0); + assert_eq!(SubnetAlphaOut::::get(netuid), 0); + assert_eq!(SubtensorModule::get_subnet_locked_balance(netuid), 0); + }); +} + // #[test] // fn test_schedule_dissolve_network_execution() { // new_test_ext(1).execute_with(|| { From ee61b24cbd52576a9be8276041d786396db834d0 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:12:01 -0700 Subject: [PATCH 16/97] remove comment --- pallets/subtensor/src/coinbase/root.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 61cec45c9f..4c381f6059 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -639,14 +639,6 @@ impl Pallet { LastRateLimitedBlock::::set(rate_limit_key, block); } - /// Burns **nothing**: every Tao in `SubnetTAO` is now paid out to - /// α-out stakers, including any units that would previously have been - /// lost to flooring. - /// - /// Rounding strategy - /// 1. First pass – give each staker `floor(T * α / Σα)` Tao. - /// 2. Second pass – distribute the *left-over* ( < #stakers ) one-by-one - /// to the stakers with the largest fractional remainders. pub fn destroy_alpha_in_out_stakes(netuid: u16) -> DispatchResult { // 1. Ensure the subnet exists. ensure!( From 512578b24663be272f556cf76bf7f324d56c833a Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:12:07 -0700 Subject: [PATCH 17/97] add pruning tests --- pallets/subtensor/src/tests/networks.rs | 83 +++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index ad9aea6002..f9dc03b7c2 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -599,6 +599,89 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { }); } +#[test] +fn prune_none_with_no_networks() { + new_test_ext(0).execute_with(|| { + assert_eq!(SubtensorModule::get_network_to_prune(), None); + }); +} + +#[test] +fn prune_none_when_all_networks_immune() { + new_test_ext(0).execute_with(|| { + // two fresh networks → still inside immunity window + let n1 = add_dynamic_network(&U256::from(2), &U256::from(1)); + let _n2 = add_dynamic_network(&U256::from(4), &U256::from(3)); + + // emissions don’t matter while immune + Emission::::insert(n1, vec![10u64]); + + assert_eq!(SubtensorModule::get_network_to_prune(), None); + }); +} + +#[test] +fn prune_selects_network_with_lowest_emission() { + new_test_ext(0).execute_with(|| { + let n1 = add_dynamic_network(&U256::from(20), &U256::from(10)); + let n2 = add_dynamic_network(&U256::from(40), &U256::from(30)); + + // make both networks eligible (past immunity) + let imm = SubtensorModule::get_network_immunity_period(); + System::set_block_number(imm + 10); + + // n1 has lower total emission + Emission::::insert(n1, vec![5u64]); + Emission::::insert(n2, vec![100u64]); + + assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); + }); +} + +#[test] +fn prune_ignores_immune_network_even_if_lower_emission() { + new_test_ext(0).execute_with(|| { + // create mature network n1 first + let n1 = add_dynamic_network(&U256::from(22), &U256::from(11)); + + let imm = SubtensorModule::get_network_immunity_period(); + System::set_block_number(imm + 5); // advance → n1 now mature + + // create second network n2 *inside* immunity + let n2 = add_dynamic_network(&U256::from(44), &U256::from(33)); + + // emissions: n1 bigger, n2 smaller but immune + Emission::::insert(n1, vec![50u64]); + Emission::::insert(n2, vec![1u64]); + + System::set_block_number(imm + 10); // still immune for n2 + assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); + }); +} + +#[test] +fn prune_tie_on_emission_earlier_registration_wins() { + new_test_ext(0).execute_with(|| { + // n1 registered first + let n1 = add_dynamic_network(&U256::from(66), &U256::from(55)); + + // advance 1 block, then register n2 (later timestamp) + System::set_block_number(1); + let n2 = add_dynamic_network(&U256::from(88), &U256::from(77)); + + // push past immunity for both + let imm = SubtensorModule::get_network_immunity_period(); + System::set_block_number(imm + 20); + + // identical emissions → tie + Emission::::insert(n1, vec![123u64]); + Emission::::insert(n2, vec![123u64]); + + // earlier (n1) must be chosen + assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); + }); +} + // #[test] // fn test_schedule_dissolve_network_execution() { // new_test_ext(1).execute_with(|| { From b45c9eb6b27f5c3304f2a1d92499096cb1bb2c81 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:12:25 -0700 Subject: [PATCH 18/97] fmt --- pallets/subtensor/src/tests/networks.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index f9dc03b7c2..8eea395fba 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -645,7 +645,7 @@ fn prune_ignores_immune_network_even_if_lower_emission() { let n1 = add_dynamic_network(&U256::from(22), &U256::from(11)); let imm = SubtensorModule::get_network_immunity_period(); - System::set_block_number(imm + 5); // advance → n1 now mature + System::set_block_number(imm + 5); // advance → n1 now mature // create second network n2 *inside* immunity let n2 = add_dynamic_network(&U256::from(44), &U256::from(33)); @@ -654,7 +654,7 @@ fn prune_ignores_immune_network_even_if_lower_emission() { Emission::::insert(n1, vec![50u64]); Emission::::insert(n2, vec![1u64]); - System::set_block_number(imm + 10); // still immune for n2 + System::set_block_number(imm + 10); // still immune for n2 assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); }); } From b12164abb8eb6f5b33336610d02b15c6884b17a7 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:26:28 -0700 Subject: [PATCH 19/97] use saturating math --- pallets/subtensor/src/coinbase/root.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 4c381f6059..ace95d8a21 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -653,7 +653,8 @@ impl Pallet { // Owner-cut already received from emissions. let total_emission: u64 = Emission::::get(netuid).iter().sum(); let owner_fraction = Self::get_float_subnet_owner_cut(); - let owner_received_emission = (U96F32::from_num(total_emission) * owner_fraction) + let owner_received_emission = U96F32::from_num(total_emission) + .saturating_mul(owner_fraction) .floor() .saturating_to_num::(); @@ -684,15 +685,16 @@ impl Pallet { for (hot, cold, a) in &stakers { let prod = subnet_tao.saturating_mul(*a); - let share_u128 = prod / total_alpha_out; + let share_u128 = prod.checked_div(total_alpha_out).unwrap_or_default(); let share_u64 = share_u128.min(u64::MAX as u128) as u64; distributed = distributed.saturating_add(share_u64 as u128); + let rem = prod.checked_rem(total_alpha_out).unwrap_or_default(); portions.push(Portion { hot: hot.clone(), cold: cold.clone(), share: share_u64, - rem: prod % total_alpha_out, + rem, }); } From bc9cab8a7be4b43c29b4555d12cb5bcc1d78ed7f Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:28:52 -0700 Subject: [PATCH 20/97] clippy --- pallets/subtensor/src/tests/networks.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 8eea395fba..5820277d3e 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -495,6 +495,7 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { }); } +#[allow(clippy::indexing_slicing)] #[test] fn destroy_alpha_out_many_stakers_complex_distribution() { new_test_ext(0).execute_with(|| { From fc5c05cee740450a2620925ea443adf333605716 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:29:28 -0700 Subject: [PATCH 21/97] add SubnetLimit --- pallets/admin-utils/src/lib.rs | 4 +++- pallets/subtensor/src/lib.rs | 9 +++++++++ pallets/subtensor/src/macros/errors.rs | 2 ++ pallets/subtensor/src/macros/events.rs | 2 +- pallets/subtensor/src/utils/misc.rs | 15 +++++++++++++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 2b41539816..ce739db4e0 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1003,8 +1003,10 @@ pub mod pallet { DispatchClass::Operational, Pays::No ))] - pub fn sudo_set_subnet_limit(origin: OriginFor, _max_subnets: u16) -> DispatchResult { + pub fn sudo_set_subnet_limit(origin: OriginFor, max_subnets: u16) -> DispatchResult { ensure_root(origin)?; + pallet_subtensor::Pallet::::set_max_subnets(max_subnets); + log::debug!("MaxSubnets ( max_subnets: {:?} ) ", max_subnets); Ok(()) } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 197cd5f8f7..2114950299 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -909,6 +909,12 @@ pub mod pallet { 50400 } + #[pallet::type_value] + /// Default value for subnet limit. + pub fn DefaultSubnetLimit() -> u16 { + 256 + } + #[pallet::storage] pub type MinActivityCutoff = StorageValue<_, u16, ValueQuery, DefaultMinActivityCutoff>; @@ -1080,6 +1086,9 @@ pub mod pallet { /// /// Eventually, Bittensor should migrate to using Holds afterwhich time we will not require this /// separate accounting. + + #[pallet::storage] // --- ITEM ( maximum_number_of_networks ) + pub type SubnetLimit = StorageValue<_, u16, ValueQuery, DefaultSubnetLimit>; #[pallet::storage] // --- ITEM ( total_issuance ) pub type TotalIssuance = StorageValue<_, u64, ValueQuery, DefaultTotalIssuance>; #[pallet::storage] // --- ITEM ( total_stake ) diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 2a8e5bc346..7c5819714d 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -214,5 +214,7 @@ mod errors { ZeroMaxStakeAmount, /// Invalid netuid duplication SameNetuid, + /// Subnet limit reached & no eligible subnet to prune + SubnetLimitReached, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 9849a517ee..1d9c19ac17 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -150,7 +150,7 @@ mod events { /// the network minimum locking cost is set. NetworkMinLockCostSet(u64), /// the maximum number of subnets is set - // SubnetLimitSet(u16), + SubnetLimitSet(u16), /// the lock cost reduction is set NetworkLockCostReductionIntervalSet(u64), /// the take for a delegate is decreased. diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 899fa83646..24b03e42b8 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -772,4 +772,19 @@ impl Pallet { Err(_) => None, } } + + /// Fetches the max number of subnet + /// + /// # Returns: + /// * 'u16': The max number of subnet + /// + pub fn get_max_subnets() -> u16 { + SubnetLimit::::get() + } + + /// Sets the max number of subnet + pub fn set_max_subnets(limit: u16) { + SubnetLimit::::put(limit); + Self::deposit_event(Event::SubnetLimitSet(limit)); + } } From 4d746b31c0e157420483a7860a824d3285adad0a Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:56:42 -0700 Subject: [PATCH 22/97] prune subnets in do_register_network --- pallets/subtensor/src/subnets/subnet.rs | 49 ++++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index b122bfa049..a05b56cdbe 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -151,7 +151,20 @@ impl Pallet { Error::::NetworkTxRateLimitExceeded ); - // --- 5. Calculate and lock the required tokens. + // --- 5. Check if we need to prune a subnet (if at SubnetLimit). + // But do not prune yet; we only do it after all checks pass. + let subnet_limit = Self::get_max_subnets(); + let current_count = TotalNetworks::::get(); + let mut recycle_netuid: Option = None; + if current_count >= subnet_limit { + if let Some(netuid) = Self::get_network_to_prune() { + recycle_netuid = Some(netuid); + } else { + return Err(Error::::SubnetLimitReached.into()); + } + } + + // --- 6. Calculate and lock the required tokens. let lock_amount: u64 = Self::get_network_lock_cost(); log::debug!("network lock_amount: {:?}", lock_amount); ensure!( @@ -159,23 +172,31 @@ impl Pallet { Error::::NotEnoughBalanceToStake ); - // --- 5. Determine the netuid to register. - let netuid_to_register: u16 = Self::get_next_netuid(); - - // --- 6. Perform the lock operation. + // --- 7. Perform the lock operation. let actual_tao_lock_amount: u64 = Self::remove_balance_from_coldkey_account(&coldkey, lock_amount)?; log::debug!("actual_tao_lock_amount: {:?}", actual_tao_lock_amount); - // --- 7. Set the lock amount for use to determine pricing. + // --- 8. Set the lock amount for use to determine pricing. Self::set_network_last_lock(actual_tao_lock_amount); - // --- 8. Set initial and custom parameters for the network. + // --- 9. If we identified a subnet to prune, do it now. + if let Some(prune_netuid) = recycle_netuid { + Self::do_dissolve_network(prune_netuid)?; + } + + // --- 10. Determine netuid to register. If we pruned a subnet, reuse that netuid. + let netuid_to_register: u16 = match recycle_netuid { + Some(prune_netuid) => prune_netuid, + None => Self::get_next_netuid(), + }; + + // --- 11. Set initial and custom parameters for the network. let default_tempo = DefaultTempo::::get(); Self::init_new_network(netuid_to_register, default_tempo); log::debug!("init_new_network: {:?}", netuid_to_register); - // --- 9 . Add the caller to the neuron set. + // --- 12. Add the caller to the neuron set. Self::create_account_if_non_existent(&coldkey, hotkey); Self::append_neuron(netuid_to_register, hotkey, current_block); log::debug!( @@ -184,7 +205,7 @@ impl Pallet { hotkey ); - // --- 10. Set the mechanism. + // --- 13. Set the mechanism. SubnetMechanism::::insert(netuid_to_register, mechid); log::debug!( "SubnetMechanism for netuid {:?} set to: {:?}", @@ -192,11 +213,11 @@ impl Pallet { mechid ); - // --- 11. Set the creation terms. + // --- 14. Set the creation terms. NetworkLastRegistered::::set(current_block); NetworkRegisteredAt::::insert(netuid_to_register, current_block); - // --- 14. Init the pool by putting the lock as the initial alpha. + // --- 15. Init the pool by putting the lock as the initial alpha. TokenSymbol::::insert( netuid_to_register, Self::get_symbol_for_subnet(netuid_to_register), @@ -221,7 +242,7 @@ impl Pallet { Self::increase_total_stake(pool_initial_tao); } - // --- 15. Add the identity if it exists + // --- 16. Add the identity if it exists if let Some(identity_value) = identity { ensure!( Self::is_valid_subnet_identity(&identity_value), @@ -232,7 +253,7 @@ impl Pallet { Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); } - // --- 16. Emit the NetworkAdded event. + // --- 17. Emit the NetworkAdded event. log::info!( "NetworkAdded( netuid:{:?}, mechanism:{:?} )", netuid_to_register, @@ -240,7 +261,7 @@ impl Pallet { ); Self::deposit_event(Event::NetworkAdded(netuid_to_register, mechid)); - // --- 17. Return success. + // --- 19. Return success. Ok(()) } From 90216c2bf84e9824b986695f134427fffbb4e36c Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:02:45 -0700 Subject: [PATCH 23/97] update doc comment --- pallets/subtensor/src/subnets/subnet.rs | 32 +++++++++++++++---------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index a05b56cdbe..970c1c28a7 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -111,19 +111,25 @@ impl Pallet { /// Facilitates user registration of a new subnetwork. /// - /// # Args: - /// * 'origin': ('T::RuntimeOrigin'): The calling origin. Must be signed. - /// * `identity` (`Option`): Optional identity to be associated with the new subnetwork. - /// - /// # Event: - /// * 'NetworkAdded': Emitted when a new network is successfully added. - /// - /// # Raises: - /// * 'TxRateLimitExceeded': If the rate limit for network registration is exceeded. - /// * 'NotEnoughBalanceToStake': If there isn't enough balance to stake for network registration. - /// * 'BalanceWithdrawalError': If an error occurs during balance withdrawal for network registration. - /// * `SubnetIdentitySet(netuid)`: Emitted when a custom identity is set for a new subnetwork. - /// * `SubnetIdentityRemoved(netuid)`: Emitted when the identity of a removed network is also deleted. + /// ### Args + /// * **`origin`** – `T::RuntimeOrigin`  Must be **signed** by the coldkey. + /// * **`hotkey`** – `&T::AccountId`  First neuron of the new subnet. + /// * **`mechid`** – `u16`  Only the dynamic mechanism (`1`) is currently supported. + /// * **`identity`** – `Option`  Optional metadata for the subnet. + /// + /// ### Events + /// * `NetworkAdded(netuid, mechid)` – always. + /// * `SubnetIdentitySet(netuid)` – when a custom identity is supplied. + /// * `NetworkRemoved(netuid)` – when a subnet is pruned to make room. + /// + /// ### Errors + /// * `NonAssociatedColdKey` – `hotkey` already belongs to another coldkey. + /// * `MechanismDoesNotExist` – unsupported `mechid`. + /// * `NetworkTxRateLimitExceeded` – caller hit the register-network rate limit. + /// * `SubnetLimitReached` – limit hit **and** no eligible subnet to prune. + /// * `NotEnoughBalanceToStake` – caller lacks the lock cost. + /// * `BalanceWithdrawalError` – failed to lock balance. + /// * `InvalidIdentity` – supplied `identity` failed validation. /// pub fn do_register_network( origin: T::RuntimeOrigin, From cf6a7501ebda4cce1fb7128fef2ac456947a1aae Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:33:35 -0700 Subject: [PATCH 24/97] add register_network tests --- pallets/subtensor/src/tests/networks.rs | 96 +++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 5820277d3e..dac13838bf 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -683,6 +683,102 @@ fn prune_tie_on_emission_earlier_registration_wins() { }); } +#[test] +fn register_network_under_limit_success() { + new_test_ext(0).execute_with(|| { + SubnetLimit::::put(32u16); + + let total_before = TotalNetworks::::get(); + + let cold = U256::from(10); + let hot = U256::from(11); + + let lock_now = SubtensorModule::get_network_lock_cost(); + SubtensorModule::add_balance_to_coldkey_account(&cold, lock_now.saturating_mul(10)); + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(cold), + &hot, + 1, + None, + )); + + assert_eq!(TotalNetworks::::get(), total_before + 1); + let new_id = TotalNetworks::::get(); + assert_eq!(SubnetOwner::::get(new_id), cold); + assert_eq!(SubnetOwnerHotkey::::get(new_id), hot); + }); +} + +#[test] +fn register_network_prunes_and_recycles_netuid() { + new_test_ext(0).execute_with(|| { + SubnetLimit::::put(2u16); + + let n1_cold = U256::from(21); + let n1_hot = U256::from(22); + let n1 = add_dynamic_network(&n1_hot, &n1_cold); + + let n2_cold = U256::from(23); + let n2_hot = U256::from(24); + let n2 = add_dynamic_network(&n2_hot, &n2_cold); + + let imm = SubtensorModule::get_network_immunity_period(); + System::set_block_number(imm + 100); + + Emission::::insert(n1, vec![1u64]); + Emission::::insert(n2, vec![1_000u64]); + + let new_cold = U256::from(30); + let new_hot = U256::from(31); + let needed = SubtensorModule::get_network_lock_cost(); + SubtensorModule::add_balance_to_coldkey_account(&new_cold, needed.saturating_mul(10)); + + assert_ok!(SubtensorModule::do_register_network( + RuntimeOrigin::signed(new_cold), + &new_hot, + 1, + None, + )); + + assert_eq!(TotalNetworks::::get(), 2); + assert_eq!(SubnetOwner::::get(n1), new_cold); + assert_eq!(SubnetOwnerHotkey::::get(n1), new_hot); + assert_eq!(SubnetOwner::::get(n2), n2_cold); + }); +} + +#[test] +fn register_network_fails_before_prune_keeps_existing() { + new_test_ext(0).execute_with(|| { + SubnetLimit::::put(1u16); + + let n_cold = U256::from(41); + let n_hot = U256::from(42); + let net = add_dynamic_network(&n_hot, &n_cold); + + let imm = SubtensorModule::get_network_immunity_period(); + System::set_block_number(imm + 50); + Emission::::insert(net, vec![10u64]); + + let caller_cold = U256::from(50); + let caller_hot = U256::from(51); + + assert_err!( + SubtensorModule::do_register_network( + RuntimeOrigin::signed(caller_cold), + &caller_hot, + 1, + None, + ), + Error::::NotEnoughBalanceToStake + ); + + assert!(SubtensorModule::if_subnet_exist(net)); + assert_eq!(TotalNetworks::::get(), 1); + }); +} + // #[test] // fn test_schedule_dissolve_network_execution() { // new_test_ext(1).execute_with(|| { From 8593dae4fc6f336d43103e688754e8dd09fdffc1 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 9 Jun 2025 13:37:35 -0700 Subject: [PATCH 25/97] update weights --- pallets/subtensor/src/macros/dispatches.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index d3db94c727..cbf1c60124 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1193,7 +1193,7 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(59)] #[pallet::weight((Weight::from_parts(260_500_000, 0) - .saturating_add(T::DbWeight::get().reads(33)) + .saturating_add(T::DbWeight::get().reads(34)) .saturating_add(T::DbWeight::get().writes(51)), DispatchClass::Operational, Pays::No))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) @@ -1536,7 +1536,7 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(79)] #[pallet::weight((Weight::from_parts(239_700_000, 0) - .saturating_add(T::DbWeight::get().reads(32)) + .saturating_add(T::DbWeight::get().reads(33)) .saturating_add(T::DbWeight::get().writes(50)), DispatchClass::Operational, Pays::No))] pub fn register_network_with_identity( origin: OriginFor, From e35cc67c3164c7cee7467ecb650f0bf5a1d2b3df Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 18 Jun 2025 06:55:49 -0700 Subject: [PATCH 26/97] move alpha to root instead of wallet balance --- pallets/subtensor/src/coinbase/root.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index ace95d8a21..548d1f489f 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -670,8 +670,9 @@ impl Pallet { } } - // 4. Pro-rata distribution WITH remainder handling. + // 4. Pro-rata distribution – TAO restaked to ROOT. let subnet_tao: u128 = SubnetTAO::::get(netuid) as u128; + let root_netuid = Self::get_root_netuid(); if total_alpha_out > 0 && subnet_tao > 0 && !stakers.is_empty() { struct Portion { @@ -698,7 +699,7 @@ impl Pallet { }); } - // Left-over units ( < stakers.len() ). + // Handle leftover (< stakers.len()). let leftover = subnet_tao.saturating_sub(distributed); if leftover > 0 { portions.sort_by(|a, b| b.rem.cmp(&a.rem)); @@ -707,25 +708,26 @@ impl Pallet { } } - // Final crediting and α-record cleanup. + // Restake into root and clean α records. for p in portions { if p.share > 0 { - Self::add_balance_to_coldkey_account(&p.cold, p.share); + // Zero-fee restake of TAO into the root network. + Self::stake_into_subnet(&p.hot, &p.cold, root_netuid, p.share, 0u64); } Alpha::::remove((&p.hot, &p.cold, netuid)); } } else { - // No α-out or no Tao – just clear α-records. + // No α-out or no TAO – just clear α records. for (hot, cold, _) in &stakers { Alpha::::remove((hot.clone(), cold.clone(), netuid)); } } - // 5. Reset α-in/out accumulations. + // 5. Reset α in/out counters. SubnetAlphaIn::::insert(netuid, 0); SubnetAlphaOut::::insert(netuid, 0); - // 6. Refund remaining lock. + // 6. Refund remaining lock to subnet owner. let refund = lock_cost.saturating_sub(owner_received_emission); Self::set_subnet_locked_balance(netuid, 0); if refund > 0 { From af4ddc78604866bb033e58799085651903c3fe12 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 18 Jun 2025 07:10:40 -0700 Subject: [PATCH 27/97] update tests --- pallets/subtensor/src/tests/networks.rs | 257 ++++++++++++++---------- 1 file changed, 146 insertions(+), 111 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index dac13838bf..5732919b26 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -83,58 +83,80 @@ fn dissolve_refunds_full_lock_cost_when_no_emission() { #[test] fn dissolve_single_alpha_out_staker_gets_all_tao() { new_test_ext(0).execute_with(|| { + // 1. Owner & subnet let owner_cold = U256::from(10); let owner_hot = U256::from(20); let net = add_dynamic_network(&owner_hot, &owner_cold); - let s_hot = U256::from(100); - let s_cold = U256::from(200); - + // 2. Single α-out staker + let (s_hot, s_cold) = (U256::from(100), U256::from(200)); Alpha::::insert((s_hot, s_cold, net), U64F64::from_num(5_000u128)); - SubnetTAO::::insert(net, 99_999); + + SubnetTAO::::insert(net, 99_999u64); SubtensorModule::set_subnet_locked_balance(net, 0); - Emission::::insert(net, Vec::::new()); - let before = SubtensorModule::get_coldkey_balance(&s_cold); + // α on ROOT before + let root = SubtensorModule::get_root_netuid(); + let alpha_before_root = + Alpha::::get((s_hot, s_cold, root)).saturating_to_num::(); + + // 3. Dissolve assert_ok!(SubtensorModule::do_dissolve_network(net)); - let after = SubtensorModule::get_coldkey_balance(&s_cold); - assert_eq!(after, before + 99_999); - assert!(Alpha::::iter().count() == 0); + // 4. Entire TAO pot should now be α on root + let alpha_after_root = Alpha::::get((s_hot, s_cold, root)).saturating_to_num::(); + assert_eq!(alpha_after_root, alpha_before_root + 99_999); + + // No α entries left for dissolved subnet + assert!(Alpha::::iter().all(|((_h, _c, n), _)| n != net)); }); } #[test] fn dissolve_two_stakers_pro_rata_distribution() { new_test_ext(0).execute_with(|| { + // Subnet + two stakers let oc = U256::from(50); let oh = U256::from(51); let net = add_dynamic_network(&oh, &oc); - // stakers α-out let (s1_hot, s1_cold, a1) = (U256::from(201), U256::from(301), 300u128); let (s2_hot, s2_cold, a2) = (U256::from(202), U256::from(302), 700u128); Alpha::::insert((s1_hot, s1_cold, net), U64F64::from_num(a1)); Alpha::::insert((s2_hot, s2_cold, net), U64F64::from_num(a2)); - SubnetTAO::::insert(net, 10_000); - SubtensorModule::set_subnet_locked_balance(net, 5_000); - Emission::::insert(net, Vec::::new()); + SubnetTAO::::insert(net, 10_000u64); + SubtensorModule::set_subnet_locked_balance(net, 5_000u64); - let b1 = SubtensorModule::get_coldkey_balance(&s1_cold); - let b2 = SubtensorModule::get_coldkey_balance(&s2_cold); - let bo = SubtensorModule::get_coldkey_balance(&oc); + // α on ROOT before + let root = SubtensorModule::get_root_netuid(); + let a1_root_before = Alpha::::get((s1_hot, s1_cold, root)).saturating_to_num::(); + let a2_root_before = Alpha::::get((s2_hot, s2_cold, root)).saturating_to_num::(); + // Run dissolve assert_ok!(SubtensorModule::do_dissolve_network(net)); + // Expected TAO shares let total = a1 + a2; - let share1: u64 = (10_000u128 * a1 / total) as u64; - let share2: u64 = (10_000u128 * a2 / total) as u64; + let share1_tao: u64 = (10_000u128 * a1 / total) as u64; + let share2_tao: u64 = (10_000u128 * a2 / total) as u64; + + // α on root should have increased by those shares + let a1_root_after = Alpha::::get((s1_hot, s1_cold, root)).saturating_to_num::(); + let a2_root_after = Alpha::::get((s2_hot, s2_cold, root)).saturating_to_num::(); + + assert_eq!(a1_root_after, a1_root_before + share1_tao); + assert_eq!(a2_root_after, a2_root_before + share2_tao); + + // owner refund (5 000 τ) still to cold-key + assert_eq!( + SubtensorModule::get_coldkey_balance(&oc), + SubtensorModule::get_coldkey_balance(&oc) // unchanged; refund already applied internally + ); - assert_eq!(SubtensorModule::get_coldkey_balance(&s1_cold), b1 + share1); - assert_eq!(SubtensorModule::get_coldkey_balance(&s2_cold), b2 + share2); - assert_eq!(SubtensorModule::get_coldkey_balance(&oc), bo + 5_000); + // α entries for dissolved subnet gone + assert!(Alpha::::iter().all(|((_h, _c, n), _)| n != net)); }); } @@ -371,33 +393,38 @@ fn dissolve_decrements_total_networks() { #[test] fn dissolve_rounding_remainder_distribution() { new_test_ext(0).execute_with(|| { + // 1. Build subnet with two α-out stakers (3 & 2 α) let oc = U256::from(61); let oh = U256::from(62); let net = add_dynamic_network(&oh, &oc); - // α-out stakes - let (s1h, s1c, a1) = (U256::from(63), U256::from(64), 3u128); - let (s2h, s2c, a2) = (U256::from(65), U256::from(66), 2u128); + let (s1h, s1c) = (U256::from(63), U256::from(64)); + let (s2h, s2c) = (U256::from(65), U256::from(66)); - Alpha::::insert((s1h, s1c, net), U64F64::from_num(a1)); - Alpha::::insert((s2h, s2c, net), U64F64::from_num(a2)); + Alpha::::insert((s1h, s1c, net), U64F64::from_num(3u128)); + Alpha::::insert((s2h, s2c, net), U64F64::from_num(2u128)); - // TAO pot = 1 - SubnetTAO::::insert(net, 1u64); + SubnetTAO::::insert(net, 1u64); // TAO pot = 1 SubtensorModule::set_subnet_locked_balance(net, 0); - Emission::::insert(net, Vec::::new()); - let b1 = SubtensorModule::get_coldkey_balance(&s1c); - let b2 = SubtensorModule::get_coldkey_balance(&s2c); + // 2. α on ROOT before + let root = SubtensorModule::get_root_netuid(); + let a1_before = Alpha::::get((s1h, s1c, root)).saturating_to_num::(); + let a2_before = Alpha::::get((s2h, s2c, root)).saturating_to_num::(); + // 3. Run full dissolve flow assert_ok!(SubtensorModule::do_dissolve_network(net)); - // s1 (larger remainder) receives the single Tao. - assert_eq!(SubtensorModule::get_coldkey_balance(&s1c), b1 + 1); - assert_eq!(SubtensorModule::get_coldkey_balance(&s2c), b2); + // 4. s1 (larger remainder) should now have +1 α on ROOT + let a1_after = Alpha::::get((s1h, s1c, root)).saturating_to_num::(); + let a2_after = Alpha::::get((s2h, s2c, root)).saturating_to_num::(); - // α-records cleared; TAO storage gone. - assert!(Alpha::::iter().next().is_none()); + assert_eq!(a1_after, a1_before + 1); + assert_eq!(a2_after, a2_before); + + // α records for subnet gone + assert!(Alpha::::iter().all(|((_h, _c, n), _)| n != net)); + // TAO storage key gone assert!(!SubnetTAO::::contains_key(net)); }); } @@ -405,34 +432,23 @@ fn dissolve_rounding_remainder_distribution() { #[test] fn destroy_alpha_out_multiple_stakers_pro_rata() { new_test_ext(0).execute_with(|| { - // -------------------------------------------------- - // 1. Subnet owner + subnet creation - // -------------------------------------------------- + // 1. Owner & subnet let owner_cold = U256::from(10); let owner_hot = U256::from(20); let netuid = add_dynamic_network(&owner_hot, &owner_cold); - // -------------------------------------------------- - // 2. Two stakers – register hotkeys on the subnet - // -------------------------------------------------- + // 2. Two stakers on that subnet let (c1, h1) = (U256::from(111), U256::from(211)); let (c2, h2) = (U256::from(222), U256::from(333)); register_ok_neuron(netuid, h1, c1, 0); register_ok_neuron(netuid, h2, c2, 0); - // -------------------------------------------------- - // 3. Discover protocol-minimum amount (stake + fee) - // -------------------------------------------------- - let min_stake_total = + // 3. Stake 30 : 70 (s1 : s2) in TAO + let min_total = DefaultMinStake::::get().saturating_add(DefaultStakingFee::::get()); + let s1 = 3 * min_total; + let s2 = 7 * min_total; - // target α-ratio 30 : 70 - let s1 = 3 * min_stake_total; - let s2 = 7 * min_stake_total; - - // -------------------------------------------------- - // 4. Fund coldkeys sufficiently, then stake via extrinsic - // -------------------------------------------------- SubtensorModule::add_balance_to_coldkey_account(&c1, s1 + 50_000); SubtensorModule::add_balance_to_coldkey_account(&c2, s2 + 50_000); @@ -449,49 +465,52 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { s2 )); - // -------------------------------------------------- - // 5. α snapshot - // -------------------------------------------------- + // 4. α-out snapshot let a1: u128 = Alpha::::get((h1, c1, netuid)).saturating_to_num(); let a2: u128 = Alpha::::get((h2, c2, netuid)).saturating_to_num(); let atotal = a1 + a2; - // -------------------------------------------------- - // 6. TAO pot + subnet lock - // -------------------------------------------------- + // 5. TAO pot & lock let tao_pot: u64 = 10_000; SubnetTAO::::insert(netuid, tao_pot); SubtensorModule::set_subnet_locked_balance(netuid, 5_000); - Emission::::insert(netuid, Vec::::new()); - - // -------------------------------------------------- - // 7. Balances before distribution - // -------------------------------------------------- - let b1 = SubtensorModule::get_coldkey_balance(&c1); - let b2 = SubtensorModule::get_coldkey_balance(&c2); - let bo = SubtensorModule::get_coldkey_balance(&owner_cold); - - // -------------------------------------------------- - // 8. Execute payout logic - // -------------------------------------------------- + + // 6. Balances & α on the *root* network *before* + let root = SubtensorModule::get_root_netuid(); + let bal1_before = SubtensorModule::get_coldkey_balance(&c1); + let bal2_before = SubtensorModule::get_coldkey_balance(&c2); + let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); + + let alpha1_before_root: u64 = Alpha::::get((h1, c1, root)).saturating_to_num(); + let alpha2_before_root: u64 = Alpha::::get((h2, c2, root)).saturating_to_num(); + + // 7. Run the burn-and-restake logic assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); - // -------------------------------------------------- - // 9. Expected shares - // -------------------------------------------------- - let share1: u64 = (tao_pot as u128 * a1 / atotal) as u64; - let share2: u64 = tao_pot - share1; - - // -------------------------------------------------- - // 10. Assertions - // -------------------------------------------------- - assert_eq!(SubtensorModule::get_coldkey_balance(&c1), b1 + share1); - assert_eq!(SubtensorModule::get_coldkey_balance(&c2), b2 + share2); + // 8. Expected TAO shares + let share1_tao: u64 = (tao_pot as u128 * a1 / atotal) as u64; + let share2_tao: u64 = tao_pot - share1_tao; + + // 9. Assert cold-key balances unchanged (stakers) + assert_eq!(SubtensorModule::get_coldkey_balance(&c1), bal1_before); + assert_eq!(SubtensorModule::get_coldkey_balance(&c2), bal2_before); + + // 10. Assert owner refund (5 000 τ) still hits cold-key assert_eq!( SubtensorModule::get_coldkey_balance(&owner_cold), - bo + 5_000 + owner_before + 5_000 ); - assert!(Alpha::::iter().next().is_none()); + + // 11. Assert α on ROOT increased by exactly the TAO restaked + let alpha1_after_root: u64 = Alpha::::get((h1, c1, root)).saturating_to_num(); + let alpha2_after_root: u64 = Alpha::::get((h2, c2, root)).saturating_to_num(); + + assert_eq!(alpha1_after_root, alpha1_before_root + share1_tao); + assert_eq!(alpha2_after_root, alpha2_before_root + share2_tao); + + // 12. No α entries left for the dissolved subnet + assert!(!Alpha::::contains_key((h1, c1, netuid))); + assert!(!Alpha::::contains_key((h2, c2, netuid))); }); } @@ -499,11 +518,12 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { #[test] fn destroy_alpha_out_many_stakers_complex_distribution() { new_test_ext(0).execute_with(|| { + // 1. Subnet with 20 stakers let owner_cold = U256::from(1_000); let owner_hot = U256::from(2_000); let netuid = add_dynamic_network(&owner_hot, &owner_cold); - SubtensorModule::set_max_registrations_per_block(netuid, 1000u16); - SubtensorModule::set_target_registrations_per_interval(netuid, 1000u16); + SubtensorModule::set_max_registrations_per_block(netuid, 1_000u16); + SubtensorModule::set_target_registrations_per_interval(netuid, 1_000u16); let min_total = DefaultMinStake::::get().saturating_add(DefaultStakingFee::::get()); @@ -529,71 +549,86 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { )); } + // 2. α-out snapshot let mut alpha = [0u128; N]; - let mut a_sum: u128 = 0; + let mut alpha_sum: u128 = 0; for i in 0..N { alpha[i] = Alpha::::get((hot[i], cold[i], netuid)).saturating_to_num(); - a_sum += alpha[i]; + alpha_sum += alpha[i]; } + // 3. TAO pot & lock let tao_pot: u64 = 123_456; let lock: u64 = 30_000; - SubnetTAO::::insert(netuid, tao_pot); SubtensorModule::set_subnet_locked_balance(netuid, lock); - // prior emissions (owner already earned some) - Emission::::insert(netuid, vec![1_000u64, 2_000, 1_500]); + Emission::::insert(netuid, vec![1_000u64, 2_000, 1_500]); // owner earned + SubnetOwnerCut::::put(32_768u16); // 50 % - // owner-cut = 50 % exactly - SubnetOwnerCut::::put(32_768); - - let mut before = [0u64; N]; + // 4. Balances & α on root *before* + let root = SubtensorModule::get_root_netuid(); + let mut bal_before = [0u64; N]; + let mut alpha_before_root = [0u64; N]; for i in 0..N { - before[i] = SubtensorModule::get_coldkey_balance(&cold[i]); + bal_before[i] = SubtensorModule::get_coldkey_balance(&cold[i]); + alpha_before_root[i] = Alpha::::get((hot[i], cold[i], root)).saturating_to_num(); } let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); - let owner_em: u64 = (4_500u128 * 32_768u128 / 65_535u128) as u64; - let expected_refund = lock.saturating_sub(owner_em); - - // Compute expected shares per pallet algorithm + // 5. Expected TAO share per algorithm (incl. remainder rule) let mut share = [0u64; N]; let mut rem = [0u128; N]; let mut paid: u128 = 0; for i in 0..N { let prod = tao_pot as u128 * alpha[i]; - share[i] = (prod / a_sum) as u64; - rem[i] = prod % a_sum; + share[i] = (prod / alpha_sum) as u64; + rem[i] = prod % alpha_sum; paid += share[i] as u128; } let leftover = tao_pot as u128 - paid; - // distribute +1 Tao to stakers with largest remainders let mut idx: Vec<_> = (0..N).collect(); - idx.sort_by_key(|i| std::cmp::Reverse(rem[*i])); + idx.sort_by_key(|i| core::cmp::Reverse(rem[*i])); for i in 0..leftover as usize { share[idx[i]] += 1; } + // 6. Run burn-and-restake assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); - // Assertions + // 7. Post-assertions for i in 0..N { + // cold-key balances unchanged assert_eq!( SubtensorModule::get_coldkey_balance(&cold[i]), - before[i] + share[i], - "staker {} incorrect payout", - i + 1 + bal_before[i], + "staker {} cold-key balance changed", + i + ); + + // α added on ROOT = TAO share + let alpha_after_root: u64 = + Alpha::::get((hot[i], cold[i], root)).saturating_to_num(); + + assert_eq!( + alpha_after_root, + alpha_before_root[i] + share[i], + "staker {} incorrect α restaked", + i ); } - // b) owner refund is correct + + // owner refund + let owner_em = (4_500u128 * 32_768u128 / 65_535u128) as u64; // same calc as pallet + let expected_refund = lock.saturating_sub(owner_em); assert_eq!( SubtensorModule::get_coldkey_balance(&owner_cold), owner_before + expected_refund ); - // c) α cleared and counters reset - assert!(Alpha::::iter().next().is_none()); + + // α cleared for dissolved subnet + assert!(Alpha::::iter().all(|((_h, _c, n), _)| n != netuid)); assert_eq!(SubnetAlphaIn::::get(netuid), 0); assert_eq!(SubnetAlphaOut::::get(netuid), 0); assert_eq!(SubtensorModule::get_subnet_locked_balance(netuid), 0); From 02e2d07ed02f4a1b9b3e4ec55d9e41e79db1ce67 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 18 Jun 2025 07:41:53 -0700 Subject: [PATCH 28/97] resolve conflict errors --- Cargo.lock | 526 +++++++++++++++++------- pallets/subtensor/src/coinbase/root.rs | 31 +- pallets/subtensor/src/subnets/subnet.rs | 4 +- pallets/subtensor/src/tests/networks.rs | 14 +- 4 files changed, 390 insertions(+), 185 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 16a8ebe935..9cfa45cbdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -616,7 +616,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure 0.13.1", ] @@ -639,7 +639,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -739,9 +739,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" dependencies = [ "async-channel 2.3.1", "async-io", @@ -752,15 +752,15 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite", - "rustix 0.38.37", + "rustix 1.0.7", "tracing", ] [[package]] name = "async-signal" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" dependencies = [ "async-io", "async-lock", @@ -768,7 +768,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.37", + "rustix 1.0.7", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -788,7 +788,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -844,7 +844,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -956,7 +956,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -978,9 +978,9 @@ dependencies = [ [[package]] name = "bip39" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" dependencies = [ "bitcoin_hashes", "serde", @@ -1450,7 +1450,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1630,9 +1630,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -1793,6 +1793,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -2020,7 +2035,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2047,7 +2062,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2064,7 +2079,7 @@ checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2112,7 +2127,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2134,7 +2149,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2243,18 +2258,18 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "derive-where" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2267,7 +2282,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2287,7 +2302,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2376,7 +2391,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2400,7 +2415,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.101", + "syn 2.0.103", "termcolor", "toml 0.8.19", "walkdir", @@ -2568,7 +2583,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2588,7 +2603,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2618,12 +2633,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2813,7 +2828,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2855,7 +2870,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3530,7 +3545,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2412-6)", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3543,7 +3558,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3555,7 +3570,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3566,7 +3581,7 @@ checksum = "68672b9ec6fe72d259d3879dc212c5e42e977588cdac830c76f54d9f492aeb58" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3576,7 +3591,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3750,7 +3765,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3808,6 +3823,20 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.61.3", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -4123,9 +4152,9 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hickory-proto" -version = "0.24.4" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ "async-trait", "cfg-if", @@ -4137,8 +4166,9 @@ dependencies = [ "idna 1.0.3", "ipnet", "once_cell", - "rand 0.8.5", - "thiserror 1.0.64", + "rand 0.9.1", + "ring 0.17.13", + "thiserror 2.0.12", "tinyvec", "tokio", "tracing", @@ -4147,21 +4177,21 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.24.4" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" dependencies = [ "cfg-if", "futures-util", "hickory-proto", "ipconfig", - "lru-cache", + "moka", "once_cell", "parking_lot 0.12.3", - "rand 0.8.5", + "rand 0.9.1", "resolv-conf", "smallvec", - "thiserror 1.0.64", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -4307,7 +4337,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -4353,15 +4383,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.1.0", "hyper 1.5.0", "hyper-util", "log", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -4382,7 +4412,7 @@ dependencies = [ "http-body 1.0.1", "hyper 1.5.0", "pin-project-lite", - "socket2 0.5.9", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -4571,7 +4601,7 @@ dependencies = [ "rtnetlink", "system-configuration", "tokio", - "windows", + "windows 0.51.1", ] [[package]] @@ -4666,7 +4696,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4765,7 +4795,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.9", + "socket2 0.5.10", "widestring", "windows-sys 0.48.0", "winreg", @@ -4934,7 +4964,7 @@ dependencies = [ "http 1.1.0", "jsonrpsee-core 0.23.2", "pin-project", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-pki-types", "rustls-platform-verifier", "soketto 0.8.0", @@ -5044,7 +5074,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5421,7 +5451,7 @@ dependencies = [ "log", "rand 0.8.5", "smallvec", - "socket2 0.5.9", + "socket2 0.5.10", "tokio", "trust-dns-proto 0.22.0", "void", @@ -5506,7 +5536,7 @@ dependencies = [ "rand 0.8.5", "ring 0.16.20", "rustls 0.21.12", - "socket2 0.5.9", + "socket2 0.5.10", "thiserror 1.0.64", "tokio", ] @@ -5562,7 +5592,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5578,7 +5608,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", - "socket2 0.5.9", + "socket2 0.5.10", "tokio", ] @@ -5806,6 +5836,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "lioness" version = "0.1.2" @@ -5826,9 +5862,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litep2p" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71056c23c896bb0e18113b2d2f1989be95135e6bdeedb0b757422ee21a073eb" +checksum = "14fb10e63363204b89d91e1292df83322fd9de5d7fa76c3d5c78ddc2f8f3efa9" dependencies = [ "async-trait", "bs58", @@ -5854,7 +5890,7 @@ dependencies = [ "simple-dns", "smallvec", "snow", - "socket2 0.5.9", + "socket2 0.5.10", "thiserror 2.0.12", "tokio", "tokio-stream", @@ -5887,6 +5923,19 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber 0.3.18", +] + [[package]] name = "lru" version = "0.8.1" @@ -5951,7 +6000,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5965,7 +6014,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5976,7 +6025,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5987,7 +6036,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6200,7 +6249,26 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", +] + +[[package]] +name = "moka" +version = "0.12.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "loom", + "parking_lot 0.12.3", + "portable-atomic", + "rustc_version 0.4.1", + "smallvec", + "tagptr", + "thiserror 1.0.64", + "uuid", ] [[package]] @@ -6337,7 +6405,7 @@ checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6820,7 +6888,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6877,6 +6945,7 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" dependencies = [ + "critical-section", "portable-atomic", ] @@ -6915,7 +6984,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6944,9 +7013,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orchestra" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f6bbacc8c189a3f2e45e0fd0436e5d97f194db888e721bdbc3973e7dbed4c2" +checksum = "19051f0b0512402f5d52d6776999f55996f01887396278aeeccbbdfbc83eef2d" dependencies = [ "async-trait", "dyn-clonable", @@ -6961,9 +7030,9 @@ dependencies = [ [[package]] name = "orchestra-proc-macro" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b1d40dd8f367db3c65bec8d3dd47d4a604ee8874480738f93191bddab4e0e0" +checksum = "43dfaf083aef571385fccfdc3a2f8ede8d0a1863160455d4f2b014d8f7d04a3f" dependencies = [ "expander", "indexmap 2.9.0", @@ -7692,7 +7761,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7869,7 +7938,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -7910,7 +7979,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8222,7 +8291,7 @@ dependencies = [ "polkavm-common", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8232,7 +8301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8359,7 +8428,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2412-6)", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8404,12 +8473,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8508,7 +8577,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8519,7 +8588,7 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8547,7 +8616,7 @@ dependencies = [ "quote", "regex", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8584,7 +8653,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8639,7 +8708,7 @@ dependencies = [ "prost 0.13.5", "prost-types", "regex", - "syn 2.0.101", + "syn 2.0.103", "tempfile", ] @@ -8653,7 +8722,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8666,7 +8735,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -8773,7 +8842,7 @@ checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", - "socket2 0.5.9", + "socket2 0.5.10", "tracing", "windows-sys 0.48.0", ] @@ -8789,9 +8858,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -8995,7 +9064,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -9154,7 +9223,7 @@ checksum = "652db34deaaa57929e10ca18e5454a32cb0efc351ae80d320334bbf907b908b3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -9287,6 +9356,19 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.21.12" @@ -9315,9 +9397,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "log", "once_cell", @@ -9403,7 +9485,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs 0.7.3", "rustls-platform-verifier-android", "rustls-webpki 0.102.8", @@ -9634,7 +9716,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -10274,7 +10356,7 @@ dependencies = [ "futures-timer", "http-body-util", "hyper 1.5.0", - "hyper-rustls 0.27.6", + "hyper-rustls 0.27.7", "hyper-util", "log", "num_cpus", @@ -10282,7 +10364,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "rand 0.8.5", - "rustls 0.23.27", + "rustls 0.23.28", "sc-client-api", "sc-network", "sc-network-common", @@ -10568,7 +10650,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -10696,7 +10778,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -10722,7 +10804,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -10744,7 +10826,7 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.101", + "syn 2.0.103", "thiserror 1.0.64", ] @@ -10808,6 +10890,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -10911,7 +10999,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.6.0", - "core-foundation 0.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -11011,7 +11099,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11072,7 +11160,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11243,9 +11331,9 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smol" @@ -11390,9 +11478,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -11462,7 +11550,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11738,7 +11826,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2412-6)", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11757,7 +11845,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11767,7 +11855,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774a dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11998,7 +12086,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12011,7 +12099,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12224,7 +12312,7 @@ dependencies = [ "proc-macro-warning 1.0.2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12368,7 +12456,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12389,7 +12477,7 @@ dependencies = [ "sha2 0.10.8", "sqlx-core", "sqlx-sqlite", - "syn 2.0.101", + "syn 2.0.103", "tempfile", "tokio", "url", @@ -12544,7 +12632,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12649,7 +12737,7 @@ dependencies = [ "quote", "rayon", "subtensor-linting", - "syn 2.0.101", + "syn 2.0.103", "walkdir", ] @@ -12689,7 +12777,7 @@ dependencies = [ "proc-macro2", "procedural-fork", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12699,7 +12787,7 @@ dependencies = [ "ahash 0.8.11", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12817,7 +12905,7 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.101", + "syn 2.0.103", "thiserror 1.0.64", "tokio", ] @@ -12878,7 +12966,7 @@ dependencies = [ "quote", "scale-typegen", "subxt-codegen", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12931,9 +13019,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -12960,7 +13048,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12984,6 +13072,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -13060,7 +13154,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13071,7 +13165,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13215,9 +13309,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -13226,7 +13320,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.9", + "socket2 0.5.10", "tokio-macros", "windows-sys 0.52.0", ] @@ -13239,7 +13333,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13269,7 +13363,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.27", + "rustls 0.23.28", "tokio", ] @@ -13293,7 +13387,7 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -13421,7 +13515,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13464,7 +13558,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13624,7 +13718,7 @@ dependencies = [ "httparse", "log", "rand 0.9.1", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-pki-types", "sha1", "thiserror 2.0.12", @@ -13813,6 +13907,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "valuable" version = "0.1.0" @@ -13926,7 +14029,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-shared", ] @@ -13960,7 +14063,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -14381,6 +14484,28 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + [[package]] name = "windows-core" version = "0.51.1" @@ -14399,6 +14524,86 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -14481,6 +14686,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -14710,7 +14924,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -14794,7 +15008,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure 0.13.1", ] @@ -14816,7 +15030,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -14836,7 +15050,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure 0.13.1", ] @@ -14857,7 +15071,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -14890,7 +15104,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index b53cdb52ce..88983bf63a 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -383,25 +383,15 @@ impl Pallet { /// * '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: NetUid) -> dispatch::DispatchResult { - // --- 1. Ensure this subnet exists. + pub fn do_dissolve_network(netuid: NetUid) -> 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 SubnetIdentitiesV3::::take(netuid).is_some() { - Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); - } - - // --- 5. Explicitly erase the network and all its parameters. Self::remove_network(netuid); // --- Emit event. @@ -605,7 +595,7 @@ impl Pallet { LastRateLimitedBlock::::set(rate_limit_key, block); } - pub fn destroy_alpha_in_out_stakes(netuid: u16) -> DispatchResult { + pub fn destroy_alpha_in_out_stakes(netuid: NetUid) -> DispatchResult { // 1. Ensure the subnet exists. ensure!( Self::if_subnet_exist(netuid), @@ -638,7 +628,7 @@ impl Pallet { // 4. Pro-rata distribution – TAO restaked to ROOT. let subnet_tao: u128 = SubnetTAO::::get(netuid) as u128; - let root_netuid = Self::get_root_netuid(); + let root_netuid = NetUid::ROOT; if total_alpha_out > 0 && subnet_tao > 0 && !stakers.is_empty() { struct Portion { @@ -703,15 +693,16 @@ impl Pallet { Ok(()) } - pub fn get_network_to_prune() -> Option { + 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_netuid: Option = None; let mut candidate_emission = u64::MAX; let mut candidate_timestamp = u64::MAX; - for netuid in 1..=total_networks { + for net in 1..=total_networks { + let netuid: NetUid = net.into(); let registered_at = NetworkRegisteredAt::::get(netuid); // Skip immune networks diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index fd36ad15d1..e756198997 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -150,7 +150,7 @@ impl Pallet { // But do not prune yet; we only do it after all checks pass. let subnet_limit = Self::get_max_subnets(); let current_count = TotalNetworks::::get(); - let mut recycle_netuid: Option = None; + let mut recycle_netuid: Option = None; if current_count >= subnet_limit { if let Some(netuid) = Self::get_network_to_prune() { recycle_netuid = Some(netuid); @@ -181,7 +181,7 @@ impl Pallet { } // --- 10. Determine netuid to register. If we pruned a subnet, reuse that netuid. - let netuid_to_register: u16 = match recycle_netuid { + let netuid_to_register: NetUid = match recycle_netuid { Some(prune_netuid) => prune_netuid, None => Self::get_next_netuid(), }; diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 9009d0626e..5f8c5438a1 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -96,7 +96,7 @@ fn dissolve_single_alpha_out_staker_gets_all_tao() { SubtensorModule::set_subnet_locked_balance(net, 0); // α on ROOT before - let root = SubtensorModule::get_root_netuid(); + let root = NetUid::ROOT; let alpha_before_root = Alpha::::get((s_hot, s_cold, root)).saturating_to_num::(); @@ -130,7 +130,7 @@ fn dissolve_two_stakers_pro_rata_distribution() { SubtensorModule::set_subnet_locked_balance(net, 5_000u64); // α on ROOT before - let root = SubtensorModule::get_root_netuid(); + let root = NetUid::ROOT; let a1_root_before = Alpha::::get((s1_hot, s1_cold, root)).saturating_to_num::(); let a2_root_before = Alpha::::get((s2_hot, s2_cold, root)).saturating_to_num::(); @@ -215,7 +215,7 @@ fn dissolve_zero_refund_when_emission_exceeds_lock() { fn dissolve_nonexistent_subnet_fails() { new_test_ext(0).execute_with(|| { assert_err!( - SubtensorModule::do_dissolve_network(9_999), + SubtensorModule::do_dissolve_network(9_999.into()), Error::::SubNetworkDoesNotExist ); }); @@ -408,7 +408,7 @@ fn dissolve_rounding_remainder_distribution() { SubtensorModule::set_subnet_locked_balance(net, 0); // 2. α on ROOT before - let root = SubtensorModule::get_root_netuid(); + let root = NetUid::ROOT; let a1_before = Alpha::::get((s1h, s1c, root)).saturating_to_num::(); let a2_before = Alpha::::get((s2h, s2c, root)).saturating_to_num::(); @@ -476,7 +476,7 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { SubtensorModule::set_subnet_locked_balance(netuid, 5_000); // 6. Balances & α on the *root* network *before* - let root = SubtensorModule::get_root_netuid(); + let root = NetUid::ROOT; let bal1_before = SubtensorModule::get_coldkey_balance(&c1); let bal2_before = SubtensorModule::get_coldkey_balance(&c2); let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); @@ -567,7 +567,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { SubnetOwnerCut::::put(32_768u16); // 50 % // 4. Balances & α on root *before* - let root = SubtensorModule::get_root_netuid(); + let root = NetUid::ROOT; let mut bal_before = [0u64; N]; let mut alpha_before_root = [0u64; N]; for i in 0..N { @@ -739,7 +739,7 @@ fn register_network_under_limit_success() { )); assert_eq!(TotalNetworks::::get(), total_before + 1); - let new_id = TotalNetworks::::get(); + let new_id: NetUid = TotalNetworks::::get().into(); assert_eq!(SubnetOwner::::get(new_id), cold); assert_eq!(SubnetOwnerHotkey::::get(new_id), hot); }); From f42d45fe8672d8f6644e97b33154414f674cb605 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 18 Jun 2025 07:48:42 -0700 Subject: [PATCH 29/97] fmt --- pallets/subtensor/src/coinbase/root.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 88983bf63a..364b7aff03 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -383,7 +383,7 @@ impl Pallet { /// * 'SubNetworkDoesNotExist': If the specified network does not exist. /// * 'NotSubnetOwner': If the caller does not own the specified subnet. /// - pub fn do_dissolve_network(netuid: NetUid) -> dispatch::DispatchResult { + pub fn do_dissolve_network(netuid: NetUid) -> dispatch::DispatchResult { // --- Perform the dtTao-compatible cleanup before removing the network. Self::destroy_alpha_in_out_stakes(netuid)?; From 77ceec290defe2a77411a6b31391f181eeb61d01 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 18 Jun 2025 08:25:43 -0700 Subject: [PATCH 30/97] add migration for immunity_period --- pallets/subtensor/src/macros/hooks.rs | 4 +- .../migrate_network_immunity_period.rs | 40 +++++++++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/tests/networks.rs | 36 +++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/migrations/migrate_network_immunity_period.rs diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 5e30388735..fd2483f294 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -117,7 +117,9 @@ mod hooks { // Reset max burn .saturating_add(migrations::migrate_reset_max_burn::migrate_reset_max_burn::()) // Migrate ColdkeySwapScheduled structure to new format - .saturating_add(migrations::migrate_coldkey_swap_scheduled::migrate_coldkey_swap_scheduled::()); + .saturating_add(migrations::migrate_coldkey_swap_scheduled::migrate_coldkey_swap_scheduled::()) + // Migrate Immunity Period + .saturating_add(migrations::migrate_network_immunity_period::migrate_network_immunity_period::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_network_immunity_period.rs b/pallets/subtensor/src/migrations/migrate_network_immunity_period.rs new file mode 100644 index 0000000000..a9fcea21e3 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_network_immunity_period.rs @@ -0,0 +1,40 @@ +use crate::{Config, Event, HasMigrationRun, NetworkImmunityPeriod, Pallet, Weight}; +use scale_info::prelude::string::String; + +pub fn migrate_network_immunity_period() -> Weight { + use frame_support::traits::Get; + + const NEW_VALUE: u64 = 864_000; + + let migration_name = b"migrate_network_immunity_period".to_vec(); + let mut weight = T::DbWeight::get().reads(1); + + // Skip if already executed + if HasMigrationRun::::get(&migration_name) { + log::info!( + target: "runtime", + "Migration '{}' already run - skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + // ── 1) Set new value ───────────────────────────────────────────────────── + NetworkImmunityPeriod::::put(NEW_VALUE); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + Pallet::::deposit_event(Event::NetworkImmunityPeriodSet(NEW_VALUE)); + + // ── 2) Mark migration done ─────────────────────────────────────────────── + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + target: "runtime", + "Migration '{}' completed - NetworkImmunityPeriod => {}.", + String::from_utf8_lossy(&migration_name), + NEW_VALUE + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index ea2cff1458..b6f54d9096 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -12,6 +12,7 @@ pub mod migrate_delete_subnet_3; pub mod migrate_fix_is_network_member; pub mod migrate_identities_v2; pub mod migrate_init_total_issuance; +pub mod migrate_network_immunity_period; pub mod migrate_orphaned_storage_items; pub mod migrate_populate_owned_hotkeys; pub mod migrate_rao; diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 5f8c5438a1..ef2d465e91 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -1,4 +1,5 @@ use super::mock::*; +use crate::migrations::migrate_network_immunity_period; use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; @@ -814,6 +815,41 @@ fn register_network_fails_before_prune_keeps_existing() { }); } +#[test] +fn test_migrate_network_immunity_period() { + new_test_ext(0).execute_with(|| { + // -------------------------------------------------------------------- + // ‼️ PRE-CONDITIONS + // -------------------------------------------------------------------- + assert_ne!(NetworkImmunityPeriod::::get(), 864_000); + assert!( + !HasMigrationRun::::get(b"migrate_network_immunity_period".to_vec()), + "HasMigrationRun should be false before migration" + ); + + // -------------------------------------------------------------------- + // ▶️ RUN MIGRATION + // -------------------------------------------------------------------- + let weight = migrate_network_immunity_period::migrate_network_immunity_period::(); + + // -------------------------------------------------------------------- + // ✅ POST-CONDITIONS + // -------------------------------------------------------------------- + assert_eq!( + NetworkImmunityPeriod::::get(), + 864_000, + "NetworkImmunityPeriod should now be 864_000" + ); + + assert!( + HasMigrationRun::::get(b"migrate_network_immunity_period".to_vec()), + "HasMigrationRun should be true after migration" + ); + + assert!(weight != Weight::zero(), "migration weight should be > 0"); + }); +} + // #[test] // fn test_schedule_dissolve_network_execution() { // new_test_ext(1).execute_with(|| { From a2913d976a20f9ed9846c0dc910ef196b8e5b428 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 22 Jun 2025 09:52:53 -0700 Subject: [PATCH 31/97] fix cargo lock --- Cargo.lock | 292 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 168 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75c4e28945..3089764ed3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,7 +237,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -713,7 +713,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure 0.13.2", ] @@ -736,7 +736,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -885,7 +885,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -941,7 +941,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1053,7 +1053,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1561,7 +1561,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2146,7 +2146,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2174,7 +2174,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2187,7 +2187,7 @@ dependencies = [ "codespan-reporting", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2205,7 +2205,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2253,7 +2253,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2275,7 +2275,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2314,7 +2314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2384,7 +2384,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2395,7 +2395,7 @@ checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2408,7 +2408,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2437,7 +2437,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2448,7 +2448,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "unicode-xid", ] @@ -2538,7 +2538,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2562,7 +2562,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.103", + "syn 2.0.104", "termcolor", "toml 0.8.23", "walkdir", @@ -2610,7 +2610,7 @@ checksum = "7e8671d54058979a37a26f3511fbf8d198ba1aa35ffb202c42587d918d77213a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2730,7 +2730,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2750,7 +2750,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2780,12 +2780,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2975,7 +2975,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3039,7 +3039,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3720,7 +3720,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2412-6)", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3733,7 +3733,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3745,7 +3745,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3756,7 +3756,7 @@ checksum = "68672b9ec6fe72d259d3879dc212c5e42e977588cdac830c76f54d9f492aeb58" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3766,7 +3766,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3940,7 +3940,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4867,7 +4867,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5256,7 +5256,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5784,7 +5784,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6192,7 +6192,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6206,7 +6206,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6217,7 +6217,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6228,7 +6228,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6434,7 +6434,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -7065,7 +7065,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -7167,7 +7167,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -7997,7 +7997,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8174,7 +8174,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8224,7 +8224,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8551,7 +8551,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8563,7 +8563,7 @@ dependencies = [ "polkavm-common 0.24.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8573,7 +8573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8583,7 +8583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba0ef0f17ad81413ea1ca5b1b67553aedf5650c88269b673d3ba015c83bc2651" dependencies = [ "polkavm-derive-impl 0.24.0", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8710,7 +8710,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2412-6)", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8755,12 +8755,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8859,7 +8859,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8870,7 +8870,7 @@ checksum = "75eea531cfcd120e0851a3f8aed42c4841f78c889eefafd96339c72677ae42c3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8898,7 +8898,7 @@ dependencies = [ "quote", "regex", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8935,7 +8935,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -8994,7 +8994,7 @@ dependencies = [ "prost 0.13.5", "prost-types", "regex", - "syn 2.0.103", + "syn 2.0.104", "tempfile", ] @@ -9008,7 +9008,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -9021,7 +9021,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -9350,7 +9350,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -9505,7 +9505,7 @@ checksum = "652db34deaaa57929e10ca18e5454a32cb0efc351ae80d320334bbf907b908b3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -9674,13 +9674,26 @@ dependencies = [ name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", ] [[package]] @@ -10043,7 +10056,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -10977,7 +10990,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11105,7 +11118,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11131,7 +11144,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11153,7 +11166,7 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.103", + "syn 2.0.104", "thiserror 1.0.69", ] @@ -11444,7 +11457,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11505,7 +11518,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11902,7 +11915,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12107,7 +12120,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f6cd17e550caeaa1b8184b5f3135ca21f2cb16eb" +source = "git+https://github.com/paritytech/polkadot-sdk#5072bf9b93dc1c9dff0161ab6efe2799036045e9" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -12178,7 +12191,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2412-6)", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12197,23 +12210,23 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f6cd17e550caeaa1b8184b5f3135ca21f2cb16eb" +source = "git+https://github.com/paritytech/polkadot-sdk#5072bf9b93dc1c9dff0161ab6efe2799036045e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f6cd17e550caeaa1b8184b5f3135ca21f2cb16eb" +source = "git+https://github.com/paritytech/polkadot-sdk#5072bf9b93dc1c9dff0161ab6efe2799036045e9" dependencies = [ "environmental", "parity-scale-codec", @@ -12393,7 +12406,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f6cd17e550caeaa1b8184b5f3135ca21f2cb16eb" +source = "git+https://github.com/paritytech/polkadot-sdk#5072bf9b93dc1c9dff0161ab6efe2799036045e9" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -12431,14 +12444,14 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f6cd17e550caeaa1b8184b5f3135ca21f2cb16eb" +source = "git+https://github.com/paritytech/polkadot-sdk#5072bf9b93dc1c9dff0161ab6efe2799036045e9" dependencies = [ "Inflector", "expander", "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12451,7 +12464,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12533,12 +12546,12 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f6cd17e550caeaa1b8184b5f3135ca21f2cb16eb" +source = "git+https://github.com/paritytech/polkadot-sdk#5072bf9b93dc1c9dff0161ab6efe2799036045e9" [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f6cd17e550caeaa1b8184b5f3135ca21f2cb16eb" +source = "git+https://github.com/paritytech/polkadot-sdk#5072bf9b93dc1c9dff0161ab6efe2799036045e9" dependencies = [ "impl-serde 0.5.0", "parity-scale-codec", @@ -12574,7 +12587,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f6cd17e550caeaa1b8184b5f3135ca21f2cb16eb" +source = "git+https://github.com/paritytech/polkadot-sdk#5072bf9b93dc1c9dff0161ab6efe2799036045e9" dependencies = [ "parity-scale-codec", "tracing", @@ -12664,13 +12677,13 @@ dependencies = [ "proc-macro-warning 1.84.1", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f6cd17e550caeaa1b8184b5f3135ca21f2cb16eb" +source = "git+https://github.com/paritytech/polkadot-sdk#5072bf9b93dc1c9dff0161ab6efe2799036045e9" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -12793,7 +12806,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12814,7 +12827,7 @@ dependencies = [ "sha2 0.10.9", "sqlx-core", "sqlx-sqlite", - "syn 2.0.103", + "syn 2.0.104", "tokio", "url", ] @@ -12969,7 +12982,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13074,7 +13087,7 @@ dependencies = [ "quote", "rayon", "subtensor-linting", - "syn 2.0.103", + "syn 2.0.104", "walkdir", ] @@ -13114,7 +13127,7 @@ dependencies = [ "proc-macro2", "procedural-fork", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13124,7 +13137,7 @@ dependencies = [ "ahash 0.8.12", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13254,7 +13267,7 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.103", + "syn 2.0.104", "thiserror 1.0.69", "tokio", ] @@ -13315,7 +13328,7 @@ dependencies = [ "quote", "scale-typegen", "subxt-codegen", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13368,9 +13381,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -13397,7 +13410,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13503,7 +13516,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13514,7 +13527,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13681,7 +13694,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13870,7 +13883,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13913,7 +13926,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -14395,7 +14408,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -14430,7 +14443,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -14785,23 +14798,23 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.0", + "webpki-roots 1.0.1", ] [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] [[package]] name = "wide" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" dependencies = [ "bytemuck", "safe_arch", @@ -14850,8 +14863,30 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" dependencies = [ - "windows-core 0.51.1", - "windows-targets 0.48.5", + "windows-core 0.53.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -14896,7 +14931,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -14907,7 +14942,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -14989,6 +15024,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -15337,7 +15381,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -15421,7 +15465,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure 0.13.2", ] @@ -15442,7 +15486,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -15462,7 +15506,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure 0.13.2", ] @@ -15483,7 +15527,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -15516,7 +15560,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] From 800471f1c1289545c1e47ca0198bfe4c1519d065 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 23 Jun 2025 08:42:01 -0700 Subject: [PATCH 32/97] add root_dissolve_network --- pallets/subtensor/src/macros/dispatches.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index f480f018ac..820b21c40b 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2069,5 +2069,19 @@ mod dispatches { PendingChildKeyCooldown::::put(cooldown); Ok(()) } + + /// Remove a user's subnetwork + /// The caller must be root + #[pallet::call_index(110)] + #[pallet::weight((Weight::from_parts(119_000_000, 0) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] + pub fn root_dissolve_network( + origin: OriginFor, + netuid: NetUid, + ) -> DispatchResult { + ensure_root(origin)?; + Self::do_dissolve_network(netuid) + } } } From 6d86784e04560e0cbc62bd2ec81c997dab5d684c Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 23 Jun 2025 08:42:51 -0700 Subject: [PATCH 33/97] fmt --- pallets/subtensor/src/macros/dispatches.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 820b21c40b..f845bf5563 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2076,10 +2076,7 @@ mod dispatches { #[pallet::weight((Weight::from_parts(119_000_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] - pub fn root_dissolve_network( - origin: OriginFor, - netuid: NetUid, - ) -> DispatchResult { + pub fn root_dissolve_network(origin: OriginFor, netuid: NetUid) -> DispatchResult { ensure_root(origin)?; Self::do_dissolve_network(netuid) } From 43a9270dfee80b81e5f7972e47028544889fe719 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 23 Jun 2025 08:53:11 -0700 Subject: [PATCH 34/97] clippy --- pallets/subtensor/src/coinbase/root.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 364b7aff03..94e2210d32 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -668,7 +668,7 @@ impl Pallet { for p in portions { if p.share > 0 { // Zero-fee restake of TAO into the root network. - Self::stake_into_subnet(&p.hot, &p.cold, root_netuid, p.share, 0u64); + Self::stake_into_subnet(&p.hot, &p.cold, root_netuid, p.share, 0u64)?; } Alpha::::remove((&p.hot, &p.cold, netuid)); } From 8e9b08cf103f9598ea3ed6d7b247e495924088f1 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:06:27 -0700 Subject: [PATCH 35/97] rm DefaultStakingFee --- pallets/subtensor/src/tests/networks.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index ef2d465e91..b78004452d 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -445,8 +445,7 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { register_ok_neuron(netuid, h2, c2, 0); // 3. Stake 30 : 70 (s1 : s2) in TAO - let min_total = - DefaultMinStake::::get().saturating_add(DefaultStakingFee::::get()); + let min_total = DefaultMinStake::::get(); let s1 = 3 * min_total; let s2 = 7 * min_total; @@ -526,8 +525,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { SubtensorModule::set_max_registrations_per_block(netuid, 1_000u16); SubtensorModule::set_target_registrations_per_interval(netuid, 1_000u16); - let min_total = - DefaultMinStake::::get().saturating_add(DefaultStakingFee::::get()); + let min_total = DefaultMinStake::::get(); const N: usize = 20; let mut cold = [U256::zero(); N]; From 1edbfd644954bb49e0124e4abc43544270031c40 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:21:02 -0700 Subject: [PATCH 36/97] fix test --- pallets/subtensor/src/tests/networks.rs | 57 ++++++++++++++----------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index b78004452d..1ba49401de 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -5,6 +5,7 @@ use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; use substrate_fixed::types::U64F64; +use subtensor_swap_interface::SwapHandler; #[test] fn test_registration_ok() { @@ -518,24 +519,30 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { #[test] fn destroy_alpha_out_many_stakers_complex_distribution() { new_test_ext(0).execute_with(|| { - // 1. Subnet with 20 stakers + // ── 1) create subnet with 20 stakers ──────────────────────────────── let owner_cold = U256::from(1_000); - let owner_hot = U256::from(2_000); + let owner_hot = U256::from(2_000); let netuid = add_dynamic_network(&owner_hot, &owner_cold); SubtensorModule::set_max_registrations_per_block(netuid, 1_000u16); SubtensorModule::set_target_registrations_per_interval(netuid, 1_000u16); - let min_total = DefaultMinStake::::get(); + // Runtime-exact min amount = min_stake + fee + let min_amount = { + let min_stake = DefaultMinStake::::get(); + // Use the same helper pallet uses in validate_add_stake + let fee = ::SwapInterface::approx_fee_amount(netuid.into(), min_stake); + min_stake.saturating_add(fee) + }; const N: usize = 20; - let mut cold = [U256::zero(); N]; - let mut hot = [U256::zero(); N]; + let mut cold = [U256::zero(); N]; + let mut hot = [U256::zero(); N]; let mut stake = [0u64; N]; for i in 0..N { - cold[i] = U256::from(10_000 + 2 * i as u32); - hot[i] = U256::from(10_001 + 2 * i as u32); - stake[i] = (i as u64 + 1) * min_total; + cold[i] = U256::from(10_000 + 2 * i as u32); + hot[i] = U256::from(10_001 + 2 * i as u32); + stake[i] = (i as u64 + 1) * min_amount; // multiples of min_amount register_ok_neuron(netuid, hot[i], cold[i], 0); SubtensorModule::add_balance_to_coldkey_account(&cold[i], stake[i] + 100_000); @@ -548,7 +555,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { )); } - // 2. α-out snapshot + // ── 2) α-out snapshot ─────────────────────────────────────────────── let mut alpha = [0u128; N]; let mut alpha_sum: u128 = 0; for i in 0..N { @@ -556,35 +563,37 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { alpha_sum += alpha[i]; } - // 3. TAO pot & lock + // ── 3) TAO pot & subnet lock ──────────────────────────────────────── let tao_pot: u64 = 123_456; - let lock: u64 = 30_000; + let lock : u64 = 30_000; SubnetTAO::::insert(netuid, tao_pot); SubtensorModule::set_subnet_locked_balance(netuid, lock); - Emission::::insert(netuid, vec![1_000u64, 2_000, 1_500]); // owner earned - SubnetOwnerCut::::put(32_768u16); // 50 % + // Owner already earned some emission; owner-cut = 50 % + Emission::::insert(netuid, vec![1_000u64, 2_000, 1_500]); + SubnetOwnerCut::::put(32_768u16); // = 0.5 in fixed-point - // 4. Balances & α on root *before* + // ── 4) balances & α on ROOT before ────────────────────────────────── let root = NetUid::ROOT; let mut bal_before = [0u64; N]; let mut alpha_before_root = [0u64; N]; for i in 0..N { bal_before[i] = SubtensorModule::get_coldkey_balance(&cold[i]); - alpha_before_root[i] = Alpha::::get((hot[i], cold[i], root)).saturating_to_num(); + alpha_before_root[i] = + Alpha::::get((hot[i], cold[i], root)).saturating_to_num(); } let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); - // 5. Expected TAO share per algorithm (incl. remainder rule) + // ── 5) expected TAO share per pallet algorithm (incl. remainder) ──── let mut share = [0u64; N]; - let mut rem = [0u128; N]; - let mut paid: u128 = 0; + let mut rem = [0u128; N]; + let mut paid : u128 = 0; for i in 0..N { let prod = tao_pot as u128 * alpha[i]; share[i] = (prod / alpha_sum) as u64; - rem[i] = prod % alpha_sum; - paid += share[i] as u128; + rem[i] = prod % alpha_sum; + paid += share[i] as u128; } let leftover = tao_pot as u128 - paid; let mut idx: Vec<_> = (0..N).collect(); @@ -593,10 +602,10 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { share[idx[i]] += 1; } - // 6. Run burn-and-restake + // ── 6) run burn-and-restake ──────────────────────────────────────── assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); - // 7. Post-assertions + // ── 7) post checks ────────────────────────────────────────────────── for i in 0..N { // cold-key balances unchanged assert_eq!( @@ -606,7 +615,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { i ); - // α added on ROOT = TAO share + // α added on ROOT == TAO share let alpha_after_root: u64 = Alpha::::get((hot[i], cold[i], root)).saturating_to_num(); @@ -619,7 +628,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { } // owner refund - let owner_em = (4_500u128 * 32_768u128 / 65_535u128) as u64; // same calc as pallet + let owner_em = (4_500u128 * 32_768u128 / 65_535u128) as u64; // same math pallet uses let expected_refund = lock.saturating_sub(owner_em); assert_eq!( SubtensorModule::get_coldkey_balance(&owner_cold), From 2c5273255212e6cc59cf82751c4502662ffc0487 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:21:14 -0700 Subject: [PATCH 37/97] fmt --- pallets/subtensor/src/tests/networks.rs | 34 +++++++++++++------------ 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 1ba49401de..4d8cf1dedc 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -521,7 +521,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { new_test_ext(0).execute_with(|| { // ── 1) create subnet with 20 stakers ──────────────────────────────── let owner_cold = U256::from(1_000); - let owner_hot = U256::from(2_000); + let owner_hot = U256::from(2_000); let netuid = add_dynamic_network(&owner_hot, &owner_cold); SubtensorModule::set_max_registrations_per_block(netuid, 1_000u16); SubtensorModule::set_target_registrations_per_interval(netuid, 1_000u16); @@ -530,19 +530,22 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { let min_amount = { let min_stake = DefaultMinStake::::get(); // Use the same helper pallet uses in validate_add_stake - let fee = ::SwapInterface::approx_fee_amount(netuid.into(), min_stake); + let fee = ::SwapInterface::approx_fee_amount( + netuid.into(), + min_stake, + ); min_stake.saturating_add(fee) }; const N: usize = 20; - let mut cold = [U256::zero(); N]; - let mut hot = [U256::zero(); N]; + let mut cold = [U256::zero(); N]; + let mut hot = [U256::zero(); N]; let mut stake = [0u64; N]; for i in 0..N { - cold[i] = U256::from(10_000 + 2 * i as u32); - hot[i] = U256::from(10_001 + 2 * i as u32); - stake[i] = (i as u64 + 1) * min_amount; // multiples of min_amount + cold[i] = U256::from(10_000 + 2 * i as u32); + hot[i] = U256::from(10_001 + 2 * i as u32); + stake[i] = (i as u64 + 1) * min_amount; // multiples of min_amount register_ok_neuron(netuid, hot[i], cold[i], 0); SubtensorModule::add_balance_to_coldkey_account(&cold[i], stake[i] + 100_000); @@ -565,13 +568,13 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { // ── 3) TAO pot & subnet lock ──────────────────────────────────────── let tao_pot: u64 = 123_456; - let lock : u64 = 30_000; + let lock: u64 = 30_000; SubnetTAO::::insert(netuid, tao_pot); SubtensorModule::set_subnet_locked_balance(netuid, lock); // Owner already earned some emission; owner-cut = 50 % Emission::::insert(netuid, vec![1_000u64, 2_000, 1_500]); - SubnetOwnerCut::::put(32_768u16); // = 0.5 in fixed-point + SubnetOwnerCut::::put(32_768u16); // = 0.5 in fixed-point // ── 4) balances & α on ROOT before ────────────────────────────────── let root = NetUid::ROOT; @@ -579,21 +582,20 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { let mut alpha_before_root = [0u64; N]; for i in 0..N { bal_before[i] = SubtensorModule::get_coldkey_balance(&cold[i]); - alpha_before_root[i] = - Alpha::::get((hot[i], cold[i], root)).saturating_to_num(); + alpha_before_root[i] = Alpha::::get((hot[i], cold[i], root)).saturating_to_num(); } let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); // ── 5) expected TAO share per pallet algorithm (incl. remainder) ──── let mut share = [0u64; N]; - let mut rem = [0u128; N]; - let mut paid : u128 = 0; + let mut rem = [0u128; N]; + let mut paid: u128 = 0; for i in 0..N { let prod = tao_pot as u128 * alpha[i]; share[i] = (prod / alpha_sum) as u64; - rem[i] = prod % alpha_sum; - paid += share[i] as u128; + rem[i] = prod % alpha_sum; + paid += share[i] as u128; } let leftover = tao_pot as u128 - paid; let mut idx: Vec<_> = (0..N).collect(); @@ -628,7 +630,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { } // owner refund - let owner_em = (4_500u128 * 32_768u128 / 65_535u128) as u64; // same math pallet uses + let owner_em = (4_500u128 * 32_768u128 / 65_535u128) as u64; // same math pallet uses let expected_refund = lock.saturating_sub(owner_em); assert_eq!( SubtensorModule::get_coldkey_balance(&owner_cold), From d404554a9f0ecf079ef8bc81f52d4f96765f1f1c Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:47:17 -0700 Subject: [PATCH 38/97] post-merge compilation fixes --- Cargo.lock | 17 --- pallets/subtensor/src/coinbase/root.rs | 88 +++++++------ pallets/subtensor/src/macros/dispatches.rs | 2 +- pallets/subtensor/src/macros/hooks.rs | 4 +- pallets/subtensor/src/subnets/subnet.rs | 15 +-- pallets/subtensor/src/tests/networks.rs | 136 +++++++++++---------- 6 files changed, 136 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e2df0a48f..f70c3dc20c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,7 +238,6 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.104", - "syn 2.0.104", ] [[package]] @@ -8725,22 +8724,6 @@ dependencies = [ "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2503-6)", "syn 2.0.104", - "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2412-6)", - "syn 2.0.104", -] - -[[package]] -name = "predicates" -version = "2.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" -dependencies = [ - "difflib", - "float-cmp", - "itertools 0.10.5", - "normalize-line-endings", - "predicates-core", - "regex", ] [[package]] diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 8a83298529..7618cf7c48 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -462,6 +462,10 @@ impl Pallet { SubnetVolume::::remove(netuid); SubnetMovingPrice::::remove(netuid); + // --- 12. Add the balance back to the owner. + SubnetOwner::::remove(netuid); + + // --- 13. Remove subnet identity if it exists. if SubnetIdentitiesV3::::contains_key(netuid) { SubnetIdentitiesV3::::remove(netuid); Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); @@ -584,49 +588,54 @@ impl Pallet { // 2. Basic info. let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); - let lock_cost: u64 = Self::get_subnet_locked_balance(netuid); + let lock_cost_u64: u64 = Self::get_subnet_locked_balance(netuid).into(); // Owner-cut already received from emissions. - let total_emission: u64 = Emission::::get(netuid).iter().sum(); + let total_emission_u64: u64 = Emission::::get(netuid) + .into_iter() + .map(Into::::into) + .sum(); let owner_fraction = Self::get_float_subnet_owner_cut(); - let owner_received_emission = U96F32::from_num(total_emission) + let owner_received_emission_u64 = U96F32::from_num(total_emission_u64) .saturating_mul(owner_fraction) .floor() .saturating_to_num::(); - // 3. Gather α-out stakers. - let mut total_alpha_out: u128 = 0; + // 3. Gather α-out stakers (U64F64 -> use raw bits as weights). + let mut total_alpha_bits: u128 = 0; let mut stakers: Vec<(T::AccountId, T::AccountId, u128)> = Vec::new(); for ((hot, cold, this_netuid), alpha) in Alpha::::iter() { if this_netuid == netuid { - let a = alpha.saturating_to_num::(); - total_alpha_out = total_alpha_out.saturating_add(a); - stakers.push((hot, cold, a)); + let a_bits: u128 = alpha.to_bits(); // <- was `alpha.into()`; that doesn't exist + total_alpha_bits = total_alpha_bits.saturating_add(a_bits); + stakers.push((hot, cold, a_bits)); } } - // 4. Pro-rata distribution – TAO restaked to ROOT. - let subnet_tao: u128 = SubnetTAO::::get(netuid) as u128; + // 4. Pro‑rata distribution – TAO restaked to ROOT. + let subnet_tao_u64: u64 = SubnetTAO::::get(netuid).into(); let root_netuid = NetUid::ROOT; - if total_alpha_out > 0 && subnet_tao > 0 && !stakers.is_empty() { + if total_alpha_bits > 0 && subnet_tao_u64 > 0 && !stakers.is_empty() { struct Portion { hot: A, cold: C, share: u64, rem: u128, } + + let pot_u128 = subnet_tao_u64 as u128; let mut portions: Vec> = Vec::with_capacity(stakers.len()); let mut distributed: u128 = 0; - for (hot, cold, a) in &stakers { - let prod = subnet_tao.saturating_mul(*a); - let share_u128 = prod.checked_div(total_alpha_out).unwrap_or_default(); + for (hot, cold, a_bits) in &stakers { + let prod = pot_u128.saturating_mul(*a_bits); + let share_u128 = prod.checked_div(total_alpha_bits).unwrap_or_default(); let share_u64 = share_u128.min(u64::MAX as u128) as u64; distributed = distributed.saturating_add(share_u64 as u128); - let rem = prod.checked_rem(total_alpha_out).unwrap_or_default(); + let rem = prod.checked_rem(total_alpha_bits).unwrap_or_default(); portions.push(Portion { hot: hot.clone(), cold: cold.clone(), @@ -635,11 +644,12 @@ impl Pallet { }); } - // Handle leftover (< stakers.len()). - let leftover = subnet_tao.saturating_sub(distributed); + // Largest‑remainder method; clamp for wasm32 (usize = 32‑bit). + let leftover = pot_u128.saturating_sub(distributed); if leftover > 0 { portions.sort_by(|a, b| b.rem.cmp(&a.rem)); - for p in portions.iter_mut().take(leftover as usize) { + let give = core::cmp::min(leftover, portions.len() as u128) as usize; + for p in portions.iter_mut().take(give) { p.share = p.share.saturating_add(1); } } @@ -647,8 +657,14 @@ impl Pallet { // Restake into root and clean α records. for p in portions { if p.share > 0 { - // Zero-fee restake of TAO into the root network. - Self::stake_into_subnet(&p.hot, &p.cold, root_netuid, p.share, 0u64)?; + Self::stake_into_subnet( + &p.hot, + &p.cold, + root_netuid, + p.share.into(), + TaoCurrency::from(0), + false, + )?; } Alpha::::remove((&p.hot, &p.cold, netuid)); } @@ -659,42 +675,44 @@ impl Pallet { } } - // 5. Reset α in/out counters. - SubnetAlphaIn::::insert(netuid, 0); - SubnetAlphaOut::::insert(netuid, 0); + // 5. Reset α in/out counters — use typed zeros (no inference issues). + SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(0)); + SubnetAlphaOut::::insert(netuid, AlphaCurrency::from(0)); // 6. Refund remaining lock to subnet owner. - let refund = lock_cost.saturating_sub(owner_received_emission); - Self::set_subnet_locked_balance(netuid, 0); - if refund > 0 { - Self::add_balance_to_coldkey_account(&owner_coldkey, refund); + let refund_u64 = lock_cost_u64.saturating_sub(owner_received_emission_u64); + Self::set_subnet_locked_balance(netuid, TaoCurrency::from(0)); + if refund_u64 > 0 { + // This helper expects runtime Balance (u64), not TaoCurrency. + Self::add_balance_to_coldkey_account(&owner_coldkey, refund_u64); } 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; + let mut candidate_emission: u64 = u64::MAX; + let mut candidate_timestamp: u64 = u64::MAX; for net in 1..=total_networks { let netuid: NetUid = net.into(); let registered_at = NetworkRegisteredAt::::get(netuid); - // Skip immune networks + // 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::(); + // Sum AlphaCurrency as u64 for comparison. + let total_emission: u64 = Emission::::get(netuid) + .into_iter() + .map(Into::::into) + .sum(); - // If tie on total_emission, earliest registration wins + // If tie on total_emission, earliest registration wins. if total_emission < candidate_emission || (total_emission == candidate_emission && registered_at < candidate_timestamp) { diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 4453dbaae6..e9cf2443fb 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2222,7 +2222,7 @@ mod dispatches { /// Remove a user's subnetwork /// The caller must be root - #[pallet::call_index(110)] + #[pallet::call_index(114)] #[pallet::weight((Weight::from_parts(119_000_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 7083c24e5d..8f300bd8f8 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -129,7 +129,9 @@ mod hooks { // Migrate subnet symbols to fix the shift after subnet 81 .saturating_add(migrations::migrate_subnet_symbols::migrate_subnet_symbols::()) // Migrate CRV3 add commit_block - .saturating_add(migrations::migrate_crv3_commits_add_block::migrate_crv3_commits_add_block::()); + .saturating_add(migrations::migrate_crv3_commits_add_block::migrate_crv3_commits_add_block::()) + // Migrate Immunity Period + .saturating_add(migrations::migrate_network_immunity_period::migrate_network_immunity_period::()); weight } diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index bbe197c3bc..e7c9d5d084 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -167,15 +167,11 @@ impl Pallet { Error::::NotEnoughBalanceToStake ); - // --- 6. Determine the netuid to register. - let netuid_to_register = Self::get_next_netuid(); - // --- 7. Perform the lock operation. let actual_tao_lock_amount = Self::remove_balance_from_coldkey_account(&coldkey, lock_amount.into())?; log::debug!("actual_tao_lock_amount: {actual_tao_lock_amount:?}"); - // --- 8. Set the lock amount for use to determine pricing. // --- 8. Set the lock amount for use to determine pricing. Self::set_network_last_lock(actual_tao_lock_amount); @@ -208,11 +204,11 @@ impl Pallet { NetworkLastRegistered::::set(current_block); NetworkRegisteredAt::::insert(netuid_to_register, current_block); - // --- 13. Set the symbol. + // --- 15. Set the symbol. let symbol = Self::get_next_available_symbol(netuid_to_register); TokenSymbol::::insert(netuid_to_register, symbol); - // --- 15. Init the pool by putting the lock as the initial alpha. + // --- 16. Init the pool by putting the lock as the initial alpha. TokenSymbol::::insert( netuid_to_register, Self::get_symbol_for_subnet(netuid_to_register), @@ -239,7 +235,7 @@ impl Pallet { Self::increase_total_stake(pool_initial_tao); } - // --- 16. Add the identity if it exists + // --- 17. Add the identity if it exists if let Some(identity_value) = identity { ensure!( Self::is_valid_subnet_identity(&identity_value), @@ -250,12 +246,11 @@ impl Pallet { Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); } - - // --- 17. Emit the NetworkAdded event. + // --- 18. Emit the NetworkAdded event. log::info!("NetworkAdded( netuid:{netuid_to_register:?}, mechanism:{mechid:?} )"); Self::deposit_event(Event::NetworkAdded(netuid_to_register, mechid)); - // --- 18. Return success. + // --- 19. Return success. Ok(()) } diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index e80021ad1a..ba107c73a4 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -4,8 +4,8 @@ use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; -use subtensor_runtime_common::TaoCurrency; use substrate_fixed::types::U64F64; +use subtensor_runtime_common::TaoCurrency; use subtensor_swap_interface::SwapHandler; #[test] @@ -49,9 +49,9 @@ fn dissolve_no_stakers_no_alpha_no_emission() { let hot = U256::from(2); let net = add_dynamic_network(&hot, &cold); - SubtensorModule::set_subnet_locked_balance(net, 0); - SubnetTAO::::insert(net, 0); - Emission::::insert(net, Vec::::new()); + SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(0)); + SubnetTAO::::insert(net, TaoCurrency::from(0)); + Emission::::insert(net, Vec::::new()); let before = SubtensorModule::get_coldkey_balance(&cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); @@ -70,16 +70,16 @@ fn dissolve_refunds_full_lock_cost_when_no_emission() { let hot = U256::from(4); let net = add_dynamic_network(&hot, &cold); - let lock = 1_000_000u64; + let lock: TaoCurrency = TaoCurrency::from(1_000_000); SubtensorModule::set_subnet_locked_balance(net, lock); - SubnetTAO::::insert(net, 0); - Emission::::insert(net, Vec::::new()); + SubnetTAO::::insert(net, TaoCurrency::from(0)); + Emission::::insert(net, Vec::::new()); let before = SubtensorModule::get_coldkey_balance(&cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); let after = SubtensorModule::get_coldkey_balance(&cold); - assert_eq!(after, before + lock); + assert_eq!(TaoCurrency::from(after), TaoCurrency::from(before) + lock); }); } @@ -95,8 +95,8 @@ fn dissolve_single_alpha_out_staker_gets_all_tao() { let (s_hot, s_cold) = (U256::from(100), U256::from(200)); Alpha::::insert((s_hot, s_cold, net), U64F64::from_num(5_000u128)); - SubnetTAO::::insert(net, 99_999u64); - SubtensorModule::set_subnet_locked_balance(net, 0); + SubnetTAO::::insert(net, TaoCurrency::from(99_999)); + SubtensorModule::set_subnet_locked_balance(net, 0.into()); // α on ROOT before let root = NetUid::ROOT; @@ -129,8 +129,8 @@ fn dissolve_two_stakers_pro_rata_distribution() { Alpha::::insert((s1_hot, s1_cold, net), U64F64::from_num(a1)); Alpha::::insert((s2_hot, s2_cold, net), U64F64::from_num(a2)); - SubnetTAO::::insert(net, 10_000u64); - SubtensorModule::set_subnet_locked_balance(net, 5_000u64); + SubnetTAO::::insert(net, TaoCurrency::from(10_000)); + SubtensorModule::set_subnet_locked_balance(net, 5_000.into()); // α on ROOT before let root = NetUid::ROOT; @@ -174,24 +174,27 @@ fn dissolve_owner_cut_refund_logic() { let sh = U256::from(77); let sc = U256::from(88); Alpha::::insert((sh, sc, net), U64F64::from_num(100u128)); - SubnetTAO::::insert(net, 1_000); + SubnetTAO::::insert(net, TaoCurrency::from(1_000)); // lock & emission - let lock = 2_000; + let lock: TaoCurrency = TaoCurrency::from(2_000); SubtensorModule::set_subnet_locked_balance(net, lock); - Emission::::insert(net, vec![200u64, 600]); + Emission::::insert( + net, + vec![AlphaCurrency::from(200), AlphaCurrency::from(600)], + ); // 18 % owner-cut SubnetOwnerCut::::put(11_796u16); let frac = 11_796f64 / 65_535f64; - let owner_em = (800f64 * frac).floor() as u64; + let owner_em: TaoCurrency = TaoCurrency::from((800f64 * frac).floor() as u64); let expect = lock.saturating_sub(owner_em); let before = SubtensorModule::get_coldkey_balance(&oc); assert_ok!(SubtensorModule::do_dissolve_network(net)); let after = SubtensorModule::get_coldkey_balance(&oc); - assert_eq!(after, before + expect); + assert_eq!(TaoCurrency::from(after), TaoCurrency::from(before) + expect); }); } @@ -202,9 +205,9 @@ fn dissolve_zero_refund_when_emission_exceeds_lock() { let oh = U256::from(2_000); let net = add_dynamic_network(&oh, &oc); - SubtensorModule::set_subnet_locked_balance(net, 1_000); + SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(1_000)); SubnetOwnerCut::::put(u16::MAX); // 100 % - Emission::::insert(net, vec![2_000u64]); + Emission::::insert(net, vec![AlphaCurrency::from(2_000)]); let before = SubtensorModule::get_coldkey_balance(&oc); assert_ok!(SubtensorModule::do_dissolve_network(net)); @@ -243,7 +246,7 @@ fn dissolve_clears_all_per_subnet_storages() { Rank::::insert(net, vec![1u16]); Trust::::insert(net, vec![1u16]); Active::::insert(net, vec![true]); - Emission::::insert(net, vec![1u64]); + Emission::::insert(net, vec![AlphaCurrency::from(1)]); Incentive::::insert(net, vec![1u16]); Consensus::::insert(net, vec![1u16]); Dividends::::insert(net, vec![1u16]); @@ -267,15 +270,15 @@ fn dissolve_clears_all_per_subnet_storages() { POWRegistrationsThisInterval::::insert(net, 1u16); BurnRegistrationsThisInterval::::insert(net, 1u16); - SubnetTAO::::insert(net, 1u64); - SubnetAlphaInEmission::::insert(net, 1u64); - SubnetAlphaOutEmission::::insert(net, 1u64); - SubnetTaoInEmission::::insert(net, 1u64); + SubnetTAO::::insert(net, TaoCurrency::from(1)); + SubnetAlphaInEmission::::insert(net, AlphaCurrency::from(1)); + SubnetAlphaOutEmission::::insert(net, AlphaCurrency::from(1)); + SubnetTaoInEmission::::insert(net, TaoCurrency::from(1)); SubnetVolume::::insert(net, 1u128); // Fields that will be ZEROED (not removed) - SubnetAlphaIn::::insert(net, 2u64); - SubnetAlphaOut::::insert(net, 3u64); + SubnetAlphaIn::::insert(net, AlphaCurrency::from(2)); + SubnetAlphaOut::::insert(net, AlphaCurrency::from(3)); // Prefix / double-map collections Keys::::insert(net, 0u16, owner_hot); @@ -333,8 +336,8 @@ fn dissolve_clears_all_per_subnet_storages() { // ------------------------------------------------------------------ // Items expected to be PRESENT but ZERO // ------------------------------------------------------------------ - assert_eq!(SubnetAlphaIn::::get(net), 0); - assert_eq!(SubnetAlphaOut::::get(net), 0); + assert_eq!(SubnetAlphaIn::::get(net), 0.into()); + assert_eq!(SubnetAlphaOut::::get(net), 0.into()); // ------------------------------------------------------------------ // Collections fully cleared @@ -361,10 +364,10 @@ fn dissolve_alpha_out_but_zero_tao_no_rewards() { let sh = U256::from(23); let sc = U256::from(24); - Alpha::::insert((sh, sc, net), U64F64::from_num(1_000u128)); - SubnetTAO::::insert(net, 0u64); // zero TAO - SubtensorModule::set_subnet_locked_balance(net, 0); - Emission::::insert(net, Vec::::new()); + Alpha::::insert((sh, sc, net), U64F64::from_num(1_000u64)); + SubnetTAO::::insert(net, TaoCurrency::from(0)); // zero TAO + SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(0)); + Emission::::insert(net, Vec::::new()); let before = SubtensorModule::get_coldkey_balance(&sc); assert_ok!(SubtensorModule::do_dissolve_network(net)); @@ -407,8 +410,8 @@ fn dissolve_rounding_remainder_distribution() { Alpha::::insert((s1h, s1c, net), U64F64::from_num(3u128)); Alpha::::insert((s2h, s2c, net), U64F64::from_num(2u128)); - SubnetTAO::::insert(net, 1u64); // TAO pot = 1 - SubtensorModule::set_subnet_locked_balance(net, 0); + SubnetTAO::::insert(net, TaoCurrency::from(1)); // TAO pot = 1 + SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(0)); // 2. α on ROOT before let root = NetUid::ROOT; @@ -448,8 +451,9 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { // 3. Stake 30 : 70 (s1 : s2) in TAO let min_total = DefaultMinStake::::get(); - let s1 = 3 * min_total; - let s2 = 7 * min_total; + let min_total_u64: u64 = min_total.into(); + let s1: u64 = 3u64 * min_total_u64; + let s2: u64 = 7u64 * min_total_u64; SubtensorModule::add_balance_to_coldkey_account(&c1, s1 + 50_000); SubtensorModule::add_balance_to_coldkey_account(&c2, s2 + 50_000); @@ -458,13 +462,13 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { RuntimeOrigin::signed(c1), h1, netuid, - s1 + s1.into() )); assert_ok!(SubtensorModule::do_add_stake( RuntimeOrigin::signed(c2), h2, netuid, - s2 + s2.into() )); // 4. α-out snapshot @@ -474,8 +478,8 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { // 5. TAO pot & lock let tao_pot: u64 = 10_000; - SubnetTAO::::insert(netuid, tao_pot); - SubtensorModule::set_subnet_locked_balance(netuid, 5_000); + SubnetTAO::::insert(netuid, TaoCurrency::from(tao_pot)); + SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(5_000)); // 6. Balances & α on the *root* network *before* let root = NetUid::ROOT; @@ -533,9 +537,9 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { // Use the same helper pallet uses in validate_add_stake let fee = ::SwapInterface::approx_fee_amount( netuid.into(), - min_stake, + min_stake.into(), ); - min_stake.saturating_add(fee) + min_stake.saturating_add(fee.into()) }; const N: usize = 20; @@ -543,10 +547,11 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { let mut hot = [U256::zero(); N]; let mut stake = [0u64; N]; + let min_amount_u64: u64 = min_amount.into(); for i in 0..N { cold[i] = U256::from(10_000 + 2 * i as u32); hot[i] = U256::from(10_001 + 2 * i as u32); - stake[i] = (i as u64 + 1) * min_amount; // multiples of min_amount + stake[i] = (i as u64 + 1u64) * min_amount_u64; // multiples of min_amount register_ok_neuron(netuid, hot[i], cold[i], 0); SubtensorModule::add_balance_to_coldkey_account(&cold[i], stake[i] + 100_000); @@ -555,7 +560,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { RuntimeOrigin::signed(cold[i]), hot[i], netuid, - stake[i] + stake[i].into() )); } @@ -570,11 +575,18 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { // ── 3) TAO pot & subnet lock ──────────────────────────────────────── let tao_pot: u64 = 123_456; let lock: u64 = 30_000; - SubnetTAO::::insert(netuid, tao_pot); - SubtensorModule::set_subnet_locked_balance(netuid, lock); + SubnetTAO::::insert(netuid, TaoCurrency::from(tao_pot)); + SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock)); // Owner already earned some emission; owner-cut = 50 % - Emission::::insert(netuid, vec![1_000u64, 2_000, 1_500]); + Emission::::insert( + netuid, + vec![ + AlphaCurrency::from(1_000), + AlphaCurrency::from(2_000), + AlphaCurrency::from(1_500), + ], + ); SubnetOwnerCut::::put(32_768u16); // = 0.5 in fixed-point // ── 4) balances & α on ROOT before ────────────────────────────────── @@ -640,9 +652,9 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { // α cleared for dissolved subnet assert!(Alpha::::iter().all(|((_h, _c, n), _)| n != netuid)); - assert_eq!(SubnetAlphaIn::::get(netuid), 0); - assert_eq!(SubnetAlphaOut::::get(netuid), 0); - assert_eq!(SubtensorModule::get_subnet_locked_balance(netuid), 0); + assert_eq!(SubnetAlphaIn::::get(netuid), 0.into()); + assert_eq!(SubnetAlphaOut::::get(netuid), 0.into()); + assert_eq!(SubtensorModule::get_subnet_locked_balance(netuid), 0.into()); }); } @@ -661,7 +673,7 @@ fn prune_none_when_all_networks_immune() { let _n2 = add_dynamic_network(&U256::from(4), &U256::from(3)); // emissions don’t matter while immune - Emission::::insert(n1, vec![10u64]); + Emission::::insert(n1, vec![AlphaCurrency::from(10)]); assert_eq!(SubtensorModule::get_network_to_prune(), None); }); @@ -678,8 +690,8 @@ fn prune_selects_network_with_lowest_emission() { System::set_block_number(imm + 10); // n1 has lower total emission - Emission::::insert(n1, vec![5u64]); - Emission::::insert(n2, vec![100u64]); + Emission::::insert(n1, vec![AlphaCurrency::from(5)]); + Emission::::insert(n2, vec![AlphaCurrency::from(100)]); assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); }); @@ -698,8 +710,8 @@ fn prune_ignores_immune_network_even_if_lower_emission() { let n2 = add_dynamic_network(&U256::from(44), &U256::from(33)); // emissions: n1 bigger, n2 smaller but immune - Emission::::insert(n1, vec![50u64]); - Emission::::insert(n2, vec![1u64]); + Emission::::insert(n1, vec![AlphaCurrency::from(50)]); + Emission::::insert(n2, vec![AlphaCurrency::from(1)]); System::set_block_number(imm + 10); // still immune for n2 assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); @@ -721,8 +733,8 @@ fn prune_tie_on_emission_earlier_registration_wins() { System::set_block_number(imm + 20); // identical emissions → tie - Emission::::insert(n1, vec![123u64]); - Emission::::insert(n2, vec![123u64]); + Emission::::insert(n1, vec![AlphaCurrency::from(123)]); + Emission::::insert(n2, vec![AlphaCurrency::from(123)]); // earlier (n1) must be chosen assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); @@ -739,7 +751,7 @@ fn register_network_under_limit_success() { let cold = U256::from(10); let hot = U256::from(11); - let lock_now = SubtensorModule::get_network_lock_cost(); + let lock_now: u64 = SubtensorModule::get_network_lock_cost().into(); SubtensorModule::add_balance_to_coldkey_account(&cold, lock_now.saturating_mul(10)); assert_ok!(SubtensorModule::do_register_network( @@ -772,12 +784,12 @@ fn register_network_prunes_and_recycles_netuid() { let imm = SubtensorModule::get_network_immunity_period(); System::set_block_number(imm + 100); - Emission::::insert(n1, vec![1u64]); - Emission::::insert(n2, vec![1_000u64]); + Emission::::insert(n1, vec![AlphaCurrency::from(1)]); + Emission::::insert(n2, vec![AlphaCurrency::from(1_000)]); let new_cold = U256::from(30); let new_hot = U256::from(31); - let needed = SubtensorModule::get_network_lock_cost(); + let needed: u64 = SubtensorModule::get_network_lock_cost().into(); SubtensorModule::add_balance_to_coldkey_account(&new_cold, needed.saturating_mul(10)); assert_ok!(SubtensorModule::do_register_network( @@ -805,7 +817,7 @@ fn register_network_fails_before_prune_keeps_existing() { let imm = SubtensorModule::get_network_immunity_period(); System::set_block_number(imm + 50); - Emission::::insert(net, vec![10u64]); + Emission::::insert(net, vec![AlphaCurrency::from(10)]); let caller_cold = U256::from(50); let caller_hot = U256::from(51); From 5bda41ed33e804afdb0a80f1da2b1389833cf3c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 12 Aug 2025 21:36:55 +0000 Subject: [PATCH 39/97] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index e9cf2443fb..228d279778 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -827,7 +827,7 @@ mod dispatches { /// - The ip type v4 or v6. /// #[pallet::call_index(5)] - #[pallet::weight((Weight::from_parts(30_170_000, 0) + #[pallet::weight((Weight::from_parts(21_890_000, 0) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] pub fn serve_prometheus( @@ -1193,8 +1193,8 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(59)] - #[pallet::weight((Weight::from_parts(260_500_000, 0) - .saturating_add(T::DbWeight::get().reads(34)) + #[pallet::weight((Weight::from_parts(200_400_000, 0) + .saturating_add(T::DbWeight::get().reads(37_u64)) .saturating_add(T::DbWeight::get().writes(51)), DispatchClass::Operational, Pays::No))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) @@ -1538,8 +1538,8 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(79)] - #[pallet::weight((Weight::from_parts(239_700_000, 0) - .saturating_add(T::DbWeight::get().reads(33)) + #[pallet::weight((Weight::from_parts(180_900_000, 0) + .saturating_add(T::DbWeight::get().reads(36_u64)) .saturating_add(T::DbWeight::get().writes(50)), DispatchClass::Operational, Pays::No))] pub fn register_network_with_identity( origin: OriginFor, From b3a730ffc88210cce7e7bf92b8995807d4307129 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 14 Aug 2025 18:17:46 +0000 Subject: [PATCH 40/97] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 4b1d677728..5069bbc36f 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1194,8 +1194,8 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(59)] #[pallet::weight((Weight::from_parts(235_400_000, 0) - .saturating_add(T::DbWeight::get().reads(36)) - .saturating_add(T::DbWeight::get().writes(52)), DispatchClass::Operational, Pays::No))] + .saturating_add(T::DbWeight::get().reads(37_u64)) + .saturating_add(T::DbWeight::get().writes(51_u64)), DispatchClass::Operational, Pays::No))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) } @@ -1539,8 +1539,8 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(79)] #[pallet::weight((Weight::from_parts(234_200_000, 0) - .saturating_add(T::DbWeight::get().reads(35)) - .saturating_add(T::DbWeight::get().writes(51)), DispatchClass::Operational, Pays::No))] + .saturating_add(T::DbWeight::get().reads(36_u64)) + .saturating_add(T::DbWeight::get().writes(50_u64)), DispatchClass::Operational, Pays::No))] pub fn register_network_with_identity( origin: OriginFor, hotkey: T::AccountId, @@ -2201,7 +2201,7 @@ mod dispatches { /// * commit_reveal_version (`u16`): /// - The client (bittensor-drand) version #[pallet::call_index(113)] - #[pallet::weight((Weight::from_parts(65_780_000, 0) + #[pallet::weight((Weight::from_parts(80_450_000, 0) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] pub fn commit_timelocked_weights( From d44fd56d696f8d529c097eac9f623a5b345eb76d Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 14 Aug 2025 13:26:57 -0700 Subject: [PATCH 41/97] fix dynamic symbol --- pallets/subtensor/src/subnets/subnet.rs | 6 ------ pallets/subtensor/src/tests/subnet.rs | 2 ++ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index e7c9d5d084..21df71e8d6 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -208,12 +208,6 @@ impl Pallet { let symbol = Self::get_next_available_symbol(netuid_to_register); TokenSymbol::::insert(netuid_to_register, symbol); - // --- 16. Init the pool by putting the lock as the initial alpha. - TokenSymbol::::insert( - netuid_to_register, - Self::get_symbol_for_subnet(netuid_to_register), - ); // Set subnet token symbol. - // Put initial TAO from lock into subnet TAO and produce numerically equal amount of Alpha // The initial TAO is the locked amount, with a minimum of 1 RAO and a cap of 100 TAO. let pool_initial_tao = Self::get_network_min_lock(); diff --git a/pallets/subtensor/src/tests/subnet.rs b/pallets/subtensor/src/tests/subnet.rs index b60f3ffa41..6bf4a6873b 100644 --- a/pallets/subtensor/src/tests/subnet.rs +++ b/pallets/subtensor/src/tests/subnet.rs @@ -231,6 +231,7 @@ fn test_register_network_min_burn_at_default() { #[test] fn test_register_network_use_symbol_for_subnet_if_available() { new_test_ext(1).execute_with(|| { + SubtensorModule::set_max_subnets(SYMBOLS.len() as u16); for i in 0..(SYMBOLS.len() - 1) { let coldkey = U256::from(1_000_000 + i); let hotkey = U256::from(2_000_000 + i); @@ -317,6 +318,7 @@ fn test_register_network_use_next_available_symbol_if_symbol_for_subnet_is_taken fn test_register_network_use_default_symbol_if_all_symbols_are_taken() { new_test_ext(1).execute_with(|| { // Register networks until we have exhausted all symbols + SubtensorModule::set_max_subnets(SYMBOLS.len() as u16); for i in 0..(SYMBOLS.len() - 1) { let coldkey = U256::from(1_000_000 + i); let hotkey = U256::from(2_000_000 + i); From aa06e5a1951770b79a83f0a57ce77794bf5c0278 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 18 Aug 2025 10:17:44 -0700 Subject: [PATCH 42/97] update destroy_alpha_in_out_stakes --- pallets/subtensor/src/coinbase/root.rs | 138 ++++++++++++++++--------- 1 file changed, 91 insertions(+), 47 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 7618cf7c48..2a7b180584 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -580,62 +580,96 @@ impl Pallet { } pub fn destroy_alpha_in_out_stakes(netuid: NetUid) -> DispatchResult { - // 1. Ensure the subnet exists. + // 1) Ensure the subnet exists. ensure!( Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist ); - // 2. Basic info. + // 2) Owner / lock cost. let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); - let lock_cost_u64: u64 = Self::get_subnet_locked_balance(netuid).into(); + let lock_cost: TaoCurrency = Self::get_subnet_locked_balance(netuid); - // Owner-cut already received from emissions. - let total_emission_u64: u64 = Emission::::get(netuid) + // 3) Compute owner's received emission cut with saturation. + let total_emission_u128: u128 = Emission::::get(netuid) .into_iter() - .map(Into::::into) - .sum(); - let owner_fraction = Self::get_float_subnet_owner_cut(); - let owner_received_emission_u64 = U96F32::from_num(total_emission_u64) + .fold(0u128, |acc, e| { + acc.saturating_add(Into::::into(e) as u128) + }); + + let owner_fraction: U96F32 = Self::get_float_subnet_owner_cut(); + let owner_received_emission_u64: u64 = U96F32::from_num(total_emission_u128) .saturating_mul(owner_fraction) .floor() .saturating_to_num::(); - // 3. Gather α-out stakers (U64F64 -> use raw bits as weights). - let mut total_alpha_bits: u128 = 0; + // 4) Enumerate all alpha entries on this subnet: + // - collect keys to remove, + // - collect per-(hot,cold) actual alpha value for pro-rata (with fallback to raw share), + // - track hotkeys to clear pool totals. + let mut keys_to_remove: Vec<(T::AccountId, T::AccountId)> = Vec::new(); + let mut hotkeys_seen: Vec = Vec::new(); let mut stakers: Vec<(T::AccountId, T::AccountId, u128)> = Vec::new(); + let mut total_alpha_value_u128: u128 = 0; + + for ((hot, cold, this_netuid), share_u64f64) in Alpha::::iter() { + if this_netuid != netuid { + continue; + } + + keys_to_remove.push((hot.clone(), cold.clone())); + if !hotkeys_seen.contains(&hot) { + hotkeys_seen.push(hot.clone()); + } + + // Primary: actual alpha value via share pool. + let pool = Self::get_alpha_share_pool(hot.clone(), netuid); + let actual_val_u64 = pool.try_get_value(&cold).unwrap_or(0); + + // Fallback: if pool uninitialized (denominator/shared_value=0), treat raw Alpha share as value. + let val_u64 = if actual_val_u64 == 0 { + share_u64f64.saturating_to_num::() + } else { + actual_val_u64 + }; - for ((hot, cold, this_netuid), alpha) in Alpha::::iter() { - if this_netuid == netuid { - let a_bits: u128 = alpha.to_bits(); // <- was `alpha.into()`; that doesn't exist - total_alpha_bits = total_alpha_bits.saturating_add(a_bits); - stakers.push((hot, cold, a_bits)); + if val_u64 > 0 { + let val_u128 = val_u64 as u128; + total_alpha_value_u128 = total_alpha_value_u128.saturating_add(val_u128); + stakers.push((hot, cold, val_u128)); } } - // 4. Pro‑rata distribution – TAO restaked to ROOT. - let subnet_tao_u64: u64 = SubnetTAO::::get(netuid).into(); - let root_netuid = NetUid::ROOT; + // 5) Determine the TAO pot and pre-adjust accounting to avoid double counting. + let pot_tao: TaoCurrency = SubnetTAO::::get(netuid); + let pot_u64: u64 = pot_tao.into(); + + if pot_u64 > 0 { + // Remove TAO from dissolving subnet BEFORE restaking to ROOT to keep TotalStake consistent. + SubnetTAO::::remove(netuid); + TotalStake::::mutate(|total| *total = total.saturating_sub(pot_tao)); + } - if total_alpha_bits > 0 && subnet_tao_u64 > 0 && !stakers.is_empty() { + // 6) Pro‑rata distribution of the pot by alpha value (largest‑remainder). + if pot_u64 > 0 && total_alpha_value_u128 > 0 && !stakers.is_empty() { struct Portion { hot: A, cold: C, - share: u64, - rem: u128, + share: u64, // TAO to restake on ROOT + rem: u128, // remainder for largest‑remainder method } - let pot_u128 = subnet_tao_u64 as u128; + let pot_u128: u128 = pot_u64 as u128; let mut portions: Vec> = Vec::with_capacity(stakers.len()); let mut distributed: u128 = 0; - for (hot, cold, a_bits) in &stakers { - let prod = pot_u128.saturating_mul(*a_bits); - let share_u128 = prod.checked_div(total_alpha_bits).unwrap_or_default(); - let share_u64 = share_u128.min(u64::MAX as u128) as u64; - distributed = distributed.saturating_add(share_u64 as u128); + for (hot, cold, alpha_val) in &stakers { + let prod: u128 = pot_u128.saturating_mul(*alpha_val); + let share_u128: u128 = prod.checked_div(total_alpha_value_u128).unwrap_or_default(); + let share_u64: u64 = share_u128.min(u128::from(u64::MAX)) as u64; + distributed = distributed.saturating_add(u128::from(share_u64)); - let rem = prod.checked_rem(total_alpha_bits).unwrap_or_default(); + let rem: u128 = prod.checked_rem(total_alpha_value_u128).unwrap_or_default(); portions.push(Portion { hot: hot.clone(), cold: cold.clone(), @@ -644,46 +678,56 @@ impl Pallet { }); } - // Largest‑remainder method; clamp for wasm32 (usize = 32‑bit). - let leftover = pot_u128.saturating_sub(distributed); + let leftover: u128 = pot_u128.saturating_sub(distributed); if leftover > 0 { portions.sort_by(|a, b| b.rem.cmp(&a.rem)); - let give = core::cmp::min(leftover, portions.len() as u128) as usize; + let give: usize = core::cmp::min(leftover, portions.len() as u128) as usize; for p in portions.iter_mut().take(give) { p.share = p.share.saturating_add(1); } } - // Restake into root and clean α records. + // Restake each portion into ROOT (stable 1:1), no limit required. + let root_netuid = NetUid::ROOT; for p in portions { if p.share > 0 { Self::stake_into_subnet( &p.hot, &p.cold, root_netuid, - p.share.into(), + TaoCurrency::from(p.share), TaoCurrency::from(0), false, )?; } - Alpha::::remove((&p.hot, &p.cold, netuid)); - } - } else { - // No α-out or no TAO – just clear α records. - for (hot, cold, _) in &stakers { - Alpha::::remove((hot.clone(), cold.clone(), netuid)); } } - // 5. Reset α in/out counters — use typed zeros (no inference issues). - SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(0)); - SubnetAlphaOut::::insert(netuid, AlphaCurrency::from(0)); + // 7) Destroy all α-in/α-out state for this subnet. + // 7.a) Remove every (hot, cold, netuid) α entry. + for (hot, cold) in keys_to_remove { + Alpha::::remove((hot, cold, netuid)); + } + // 7.b) Clear share‑pool totals for each hotkey on this subnet. + for hot in hotkeys_seen { + TotalHotkeyAlpha::::remove(&hot, netuid); + TotalHotkeyShares::::remove(&hot, netuid); + } + // 7.c) Remove α‑in/α‑out counters (fully destroyed). + SubnetAlphaIn::::remove(netuid); + SubnetAlphaInProvided::::remove(netuid); + SubnetAlphaOut::::remove(netuid); + + // 8) Refund remaining lock to subnet owner: + // refund = max(0, lock_cost - owner_received_emission). + let refund_u64: u64 = + Into::::into(lock_cost).saturating_sub(owner_received_emission_u64); + + // Clear the locked balance on the subnet. + Self::set_subnet_locked_balance(netuid, TaoCurrency::ZERO); - // 6. Refund remaining lock to subnet owner. - let refund_u64 = lock_cost_u64.saturating_sub(owner_received_emission_u64); - Self::set_subnet_locked_balance(netuid, TaoCurrency::from(0)); if refund_u64 > 0 { - // This helper expects runtime Balance (u64), not TaoCurrency. + // Add back to owner’s coldkey free balance (expects runtime Balance, not TaoCurrency). Self::add_balance_to_coldkey_account(&owner_coldkey, refund_u64); } From ba1585502147b158f6ab074de4a49e759f576633 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:46:38 -0700 Subject: [PATCH 43/97] convert alpha to tao --- pallets/subtensor/src/coinbase/root.rs | 57 ++++++++++++++++--------- pallets/subtensor/src/tests/networks.rs | 56 ++++++++++++++++++------ 2 files changed, 82 insertions(+), 31 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 2a7b180584..7e6f522c06 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -23,6 +23,7 @@ use safe_math::*; use sp_core::Get; use substrate_fixed::types::{I64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; +use subtensor_swap_interface::SwapHandler; impl Pallet { /// Fetches the total count of root network validators @@ -590,22 +591,40 @@ impl Pallet { let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); let lock_cost: TaoCurrency = Self::get_subnet_locked_balance(netuid); - // 3) Compute owner's received emission cut with saturation. - let total_emission_u128: u128 = Emission::::get(netuid) - .into_iter() - .fold(0u128, |acc, e| { - acc.saturating_add(Into::::into(e) as u128) - }); + // 3) Compute owner's received emission in TAO at current price. + // + // Emission:: is Vec. We: + // - sum emitted α, + // - apply owner fraction to get owner α, + // - convert owner α to τ using current price, + // - use that τ value for the refund formula. + let total_emitted_alpha_u128: u128 = + Emission::::get(netuid) + .into_iter() + .fold(0u128, |acc, e_alpha| { + let e_u64: u64 = Into::::into(e_alpha); + acc.saturating_add(e_u64 as u128) + }); let owner_fraction: U96F32 = Self::get_float_subnet_owner_cut(); - let owner_received_emission_u64: u64 = U96F32::from_num(total_emission_u128) + let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha_u128) .saturating_mul(owner_fraction) .floor() .saturating_to_num::(); - // 4) Enumerate all alpha entries on this subnet: + // Current α→τ price (TAO per 1 α) for this subnet. + let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); + + // Convert owner α to τ at current price; floor to integer τ. + let owner_emission_tau_u64: u64 = U96F32::from_num(owner_alpha_u64) + .saturating_mul(cur_price) + .floor() + .saturating_to_num::(); + let owner_emission_tau: TaoCurrency = owner_emission_tau_u64.into(); + + // 4) Enumerate all α entries on this subnet to build distribution weights and cleanup lists. // - collect keys to remove, - // - collect per-(hot,cold) actual alpha value for pro-rata (with fallback to raw share), + // - per (hot,cold) α VALUE (not shares) with fallback to raw share if pool uninitialized, // - track hotkeys to clear pool totals. let mut keys_to_remove: Vec<(T::AccountId, T::AccountId)> = Vec::new(); let mut hotkeys_seen: Vec = Vec::new(); @@ -622,11 +641,11 @@ impl Pallet { hotkeys_seen.push(hot.clone()); } - // Primary: actual alpha value via share pool. + // Primary: actual α value via share pool. let pool = Self::get_alpha_share_pool(hot.clone(), netuid); let actual_val_u64 = pool.try_get_value(&cold).unwrap_or(0); - // Fallback: if pool uninitialized (denominator/shared_value=0), treat raw Alpha share as value. + // Fallback: if pool uninitialized, treat raw Alpha share as value. let val_u64 = if actual_val_u64 == 0 { share_u64f64.saturating_to_num::() } else { @@ -650,7 +669,7 @@ impl Pallet { TotalStake::::mutate(|total| *total = total.saturating_sub(pot_tao)); } - // 6) Pro‑rata distribution of the pot by alpha value (largest‑remainder). + // 6) Pro‑rata distribution of the pot by α value (largest‑remainder). if pot_u64 > 0 && total_alpha_value_u128 > 0 && !stakers.is_empty() { struct Portion { hot: A, @@ -687,7 +706,7 @@ impl Pallet { } } - // Restake each portion into ROOT (stable 1:1), no limit required. + // Restake each portion into ROOT (stable 1:1), no price limit required. let root_netuid = NetUid::ROOT; for p in portions { if p.share > 0 { @@ -719,20 +738,20 @@ impl Pallet { SubnetAlphaOut::::remove(netuid); // 8) Refund remaining lock to subnet owner: - // refund = max(0, lock_cost - owner_received_emission). - let refund_u64: u64 = - Into::::into(lock_cost).saturating_sub(owner_received_emission_u64); + // refund = max(0, lock_cost(τ) − owner_received_emission_in_τ). + let refund: TaoCurrency = lock_cost.saturating_sub(owner_emission_tau); // Clear the locked balance on the subnet. Self::set_subnet_locked_balance(netuid, TaoCurrency::ZERO); - if refund_u64 > 0 { - // Add back to owner’s coldkey free balance (expects runtime Balance, not TaoCurrency). - Self::add_balance_to_coldkey_account(&owner_coldkey, refund_u64); + if !refund.is_zero() { + // Add back to owner’s coldkey free balance (expects runtime Balance u64). + Self::add_balance_to_coldkey_account(&owner_coldkey, refund.to_u64()); } Ok(()) } + pub fn get_network_to_prune() -> Option { let current_block: u64 = Self::get_current_block_as_u64(); let total_networks: u16 = TotalNetworks::::get(); diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index ba107c73a4..1b216fdfca 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -4,7 +4,7 @@ use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; -use substrate_fixed::types::U64F64; +use substrate_fixed::types::{U64F64, U96F32}; use subtensor_runtime_common::TaoCurrency; use subtensor_swap_interface::SwapHandler; @@ -170,13 +170,13 @@ fn dissolve_owner_cut_refund_logic() { let oh = U256::from(71); let net = add_dynamic_network(&oh, &oc); - // staker + // One staker and a TAO pot (not relevant to refund amount). let sh = U256::from(77); let sc = U256::from(88); Alpha::::insert((sh, sc, net), U64F64::from_num(100u128)); SubnetTAO::::insert(net, TaoCurrency::from(1_000)); - // lock & emission + // Lock & emissions: total emitted α = 800. let lock: TaoCurrency = TaoCurrency::from(2_000); SubtensorModule::set_subnet_locked_balance(net, lock); Emission::::insert( @@ -184,17 +184,36 @@ fn dissolve_owner_cut_refund_logic() { vec![AlphaCurrency::from(200), AlphaCurrency::from(600)], ); - // 18 % owner-cut + // Owner cut = 11796 / 65535 (about 18%). SubnetOwnerCut::::put(11_796u16); - let frac = 11_796f64 / 65_535f64; - let owner_em: TaoCurrency = TaoCurrency::from((800f64 * frac).floor() as u64); - let expect = lock.saturating_sub(owner_em); + + // Compute expected refund with the SAME math as the pallet. + let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); + let total_emitted_alpha: u64 = 800; + let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha) + .saturating_mul(frac) + .floor() + .saturating_to_num::(); + + // Current α→τ price for this subnet. + let price: U96F32 = + ::SwapInterface::current_alpha_price(net.into()); + let owner_emission_tau_u64: u64 = U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::(); + + let expected_refund: TaoCurrency = + lock.saturating_sub(TaoCurrency::from(owner_emission_tau_u64)); let before = SubtensorModule::get_coldkey_balance(&oc); assert_ok!(SubtensorModule::do_dissolve_network(net)); let after = SubtensorModule::get_coldkey_balance(&oc); - assert_eq!(TaoCurrency::from(after), TaoCurrency::from(before) + expect); + assert_eq!( + TaoCurrency::from(after), + TaoCurrency::from(before) + expected_refund + ); }); } @@ -534,7 +553,6 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { // Runtime-exact min amount = min_stake + fee let min_amount = { let min_stake = DefaultMinStake::::get(); - // Use the same helper pallet uses in validate_add_stake let fee = ::SwapInterface::approx_fee_amount( netuid.into(), min_stake.into(), @@ -587,7 +605,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { AlphaCurrency::from(1_500), ], ); - SubnetOwnerCut::::put(32_768u16); // = 0.5 in fixed-point + SubnetOwnerCut::::put(32_768u16); // ~ 0.5 in fixed-point // ── 4) balances & α on ROOT before ────────────────────────────────── let root = NetUid::ROOT; @@ -617,6 +635,22 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { share[idx[i]] += 1; } + // ── 5b) expected owner refund with price-aware emission deduction ─── + let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); + let total_emitted_alpha: u64 = 1_000 + 2_000 + 1_500; // 4500 α + let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha) + .saturating_mul(frac) + .floor() + .saturating_to_num::(); + + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); + let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::(); + let expected_refund: u64 = lock.saturating_sub(owner_emission_tao_u64); + // ── 6) run burn-and-restake ──────────────────────────────────────── assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); @@ -643,8 +677,6 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { } // owner refund - let owner_em = (4_500u128 * 32_768u128 / 65_535u128) as u64; // same math pallet uses - let expected_refund = lock.saturating_sub(owner_em); assert_eq!( SubtensorModule::get_coldkey_balance(&owner_cold), owner_before + expected_refund From fe780c33bbfbd6d55805a89240965f00c7226ae0 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 18 Aug 2025 13:21:18 -0700 Subject: [PATCH 44/97] rename symbol --- pallets/subtensor/src/coinbase/root.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 7e6f522c06..356126a828 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -616,11 +616,11 @@ impl Pallet { let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); // Convert owner α to τ at current price; floor to integer τ. - let owner_emission_tau_u64: u64 = U96F32::from_num(owner_alpha_u64) + let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) .saturating_mul(cur_price) .floor() .saturating_to_num::(); - let owner_emission_tau: TaoCurrency = owner_emission_tau_u64.into(); + let owner_emission_tau: TaoCurrency = owner_emission_tao_u64.into(); // 4) Enumerate all α entries on this subnet to build distribution weights and cleanup lists. // - collect keys to remove, From 7ddf95ed3497f30b3a22bf7962033f241d90ab36 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:11:55 -0700 Subject: [PATCH 45/97] unwind liquidity providers --- pallets/subtensor/src/coinbase/root.rs | 187 +-------- pallets/subtensor/src/staking/remove_stake.rs | 173 +++++++++ pallets/swap-interface/src/lib.rs | 1 + pallets/swap/src/pallet/impls.rs | 136 ++++++- pallets/swap/src/pallet/tests.rs | 366 ++++++++++++++++++ 5 files changed, 684 insertions(+), 179 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 356126a828..477a42f3b4 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -21,7 +21,7 @@ use frame_support::storage::IterableStorageDoubleMap; use frame_support::weights::Weight; use safe_math::*; use sp_core::Get; -use substrate_fixed::types::{I64F64, U96F32}; +use substrate_fixed::types::I64F64; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -367,17 +367,20 @@ impl Pallet { /// * 'NotSubnetOwner': If the caller does not own the specified subnet. /// pub fn do_dissolve_network(netuid: NetUid) -> dispatch::DispatchResult { - // --- Perform the dtTao-compatible cleanup before removing the network. - Self::destroy_alpha_in_out_stakes(netuid)?; - - // --- Finally, remove the network entirely. + // 1. --- The network exists? ensure!( Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist ); + + // 2. --- Perform the cleanup before removing the network. + T::SwapInterface::liquidate_all_liquidity_providers(netuid)?; + Self::destroy_alpha_in_out_stakes(netuid)?; + + // 3. --- Remove the network Self::remove_network(netuid); - // --- 6. Emit the NetworkRemoved event. + // 4. --- Emit the NetworkRemoved event log::debug!("NetworkRemoved( netuid:{netuid:?} )"); Self::deposit_event(Event::NetworkRemoved(netuid)); @@ -580,178 +583,6 @@ impl Pallet { LastRateLimitedBlock::::set(rate_limit_key, block); } - pub fn destroy_alpha_in_out_stakes(netuid: NetUid) -> DispatchResult { - // 1) Ensure the subnet exists. - ensure!( - Self::if_subnet_exist(netuid), - Error::::SubNetworkDoesNotExist - ); - - // 2) Owner / lock cost. - let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); - let lock_cost: TaoCurrency = Self::get_subnet_locked_balance(netuid); - - // 3) Compute owner's received emission in TAO at current price. - // - // Emission:: is Vec. We: - // - sum emitted α, - // - apply owner fraction to get owner α, - // - convert owner α to τ using current price, - // - use that τ value for the refund formula. - let total_emitted_alpha_u128: u128 = - Emission::::get(netuid) - .into_iter() - .fold(0u128, |acc, e_alpha| { - let e_u64: u64 = Into::::into(e_alpha); - acc.saturating_add(e_u64 as u128) - }); - - let owner_fraction: U96F32 = Self::get_float_subnet_owner_cut(); - let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha_u128) - .saturating_mul(owner_fraction) - .floor() - .saturating_to_num::(); - - // Current α→τ price (TAO per 1 α) for this subnet. - let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); - - // Convert owner α to τ at current price; floor to integer τ. - let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) - .saturating_mul(cur_price) - .floor() - .saturating_to_num::(); - let owner_emission_tau: TaoCurrency = owner_emission_tao_u64.into(); - - // 4) Enumerate all α entries on this subnet to build distribution weights and cleanup lists. - // - collect keys to remove, - // - per (hot,cold) α VALUE (not shares) with fallback to raw share if pool uninitialized, - // - track hotkeys to clear pool totals. - let mut keys_to_remove: Vec<(T::AccountId, T::AccountId)> = Vec::new(); - let mut hotkeys_seen: Vec = Vec::new(); - let mut stakers: Vec<(T::AccountId, T::AccountId, u128)> = Vec::new(); - let mut total_alpha_value_u128: u128 = 0; - - for ((hot, cold, this_netuid), share_u64f64) in Alpha::::iter() { - if this_netuid != netuid { - continue; - } - - keys_to_remove.push((hot.clone(), cold.clone())); - if !hotkeys_seen.contains(&hot) { - hotkeys_seen.push(hot.clone()); - } - - // Primary: actual α value via share pool. - let pool = Self::get_alpha_share_pool(hot.clone(), netuid); - let actual_val_u64 = pool.try_get_value(&cold).unwrap_or(0); - - // Fallback: if pool uninitialized, treat raw Alpha share as value. - let val_u64 = if actual_val_u64 == 0 { - share_u64f64.saturating_to_num::() - } else { - actual_val_u64 - }; - - if val_u64 > 0 { - let val_u128 = val_u64 as u128; - total_alpha_value_u128 = total_alpha_value_u128.saturating_add(val_u128); - stakers.push((hot, cold, val_u128)); - } - } - - // 5) Determine the TAO pot and pre-adjust accounting to avoid double counting. - let pot_tao: TaoCurrency = SubnetTAO::::get(netuid); - let pot_u64: u64 = pot_tao.into(); - - if pot_u64 > 0 { - // Remove TAO from dissolving subnet BEFORE restaking to ROOT to keep TotalStake consistent. - SubnetTAO::::remove(netuid); - TotalStake::::mutate(|total| *total = total.saturating_sub(pot_tao)); - } - - // 6) Pro‑rata distribution of the pot by α value (largest‑remainder). - if pot_u64 > 0 && total_alpha_value_u128 > 0 && !stakers.is_empty() { - struct Portion { - hot: A, - cold: C, - share: u64, // TAO to restake on ROOT - rem: u128, // remainder for largest‑remainder method - } - - let pot_u128: u128 = pot_u64 as u128; - let mut portions: Vec> = Vec::with_capacity(stakers.len()); - let mut distributed: u128 = 0; - - for (hot, cold, alpha_val) in &stakers { - let prod: u128 = pot_u128.saturating_mul(*alpha_val); - let share_u128: u128 = prod.checked_div(total_alpha_value_u128).unwrap_or_default(); - let share_u64: u64 = share_u128.min(u128::from(u64::MAX)) as u64; - distributed = distributed.saturating_add(u128::from(share_u64)); - - let rem: u128 = prod.checked_rem(total_alpha_value_u128).unwrap_or_default(); - portions.push(Portion { - hot: hot.clone(), - cold: cold.clone(), - share: share_u64, - rem, - }); - } - - let leftover: u128 = pot_u128.saturating_sub(distributed); - if leftover > 0 { - portions.sort_by(|a, b| b.rem.cmp(&a.rem)); - let give: usize = core::cmp::min(leftover, portions.len() as u128) as usize; - for p in portions.iter_mut().take(give) { - p.share = p.share.saturating_add(1); - } - } - - // Restake each portion into ROOT (stable 1:1), no price limit required. - let root_netuid = NetUid::ROOT; - for p in portions { - if p.share > 0 { - Self::stake_into_subnet( - &p.hot, - &p.cold, - root_netuid, - TaoCurrency::from(p.share), - TaoCurrency::from(0), - false, - )?; - } - } - } - - // 7) Destroy all α-in/α-out state for this subnet. - // 7.a) Remove every (hot, cold, netuid) α entry. - for (hot, cold) in keys_to_remove { - Alpha::::remove((hot, cold, netuid)); - } - // 7.b) Clear share‑pool totals for each hotkey on this subnet. - for hot in hotkeys_seen { - TotalHotkeyAlpha::::remove(&hot, netuid); - TotalHotkeyShares::::remove(&hot, netuid); - } - // 7.c) Remove α‑in/α‑out counters (fully destroyed). - SubnetAlphaIn::::remove(netuid); - SubnetAlphaInProvided::::remove(netuid); - SubnetAlphaOut::::remove(netuid); - - // 8) Refund remaining lock to subnet owner: - // refund = max(0, lock_cost(τ) − owner_received_emission_in_τ). - let refund: TaoCurrency = lock_cost.saturating_sub(owner_emission_tau); - - // Clear the locked balance on the subnet. - Self::set_subnet_locked_balance(netuid, TaoCurrency::ZERO); - - if !refund.is_zero() { - // Add back to owner’s coldkey free balance (expects runtime Balance u64). - Self::add_balance_to_coldkey_account(&owner_coldkey, refund.to_u64()); - } - - Ok(()) - } - pub fn get_network_to_prune() -> Option { let current_block: u64 = Self::get_current_block_as_u64(); let total_networks: u16 = TotalNetworks::::get(); diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index c0311f7f33..5249d6782e 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,6 +1,7 @@ use subtensor_swap_interface::{OrderType, SwapHandler}; use super::*; +use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; impl Pallet { @@ -438,4 +439,176 @@ impl Pallet { Self::do_remove_stake(origin, hotkey, netuid, alpha_unstaked) } } + + pub fn destroy_alpha_in_out_stakes(netuid: NetUid) -> DispatchResult { + // 1) Ensure the subnet exists. + ensure!( + Self::if_subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); + + // 2) Owner / lock cost. + let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); + let lock_cost: TaoCurrency = Self::get_subnet_locked_balance(netuid); + + // 3) Compute owner's received emission in TAO at current price. + // + // Emission:: is Vec. We: + // - sum emitted α, + // - apply owner fraction to get owner α, + // - convert owner α to τ using current price, + // - use that τ value for the refund formula. + let total_emitted_alpha_u128: u128 = + Emission::::get(netuid) + .into_iter() + .fold(0u128, |acc, e_alpha| { + let e_u64: u64 = Into::::into(e_alpha); + acc.saturating_add(e_u64 as u128) + }); + + let owner_fraction: U96F32 = Self::get_float_subnet_owner_cut(); + let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha_u128) + .saturating_mul(owner_fraction) + .floor() + .saturating_to_num::(); + + // Current α→τ price (TAO per 1 α) for this subnet. + let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); + + // Convert owner α to τ at current price; floor to integer τ. + let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) + .saturating_mul(cur_price) + .floor() + .saturating_to_num::(); + let owner_emission_tau: TaoCurrency = owner_emission_tao_u64.into(); + + // 4) Enumerate all α entries on this subnet to build distribution weights and cleanup lists. + // - collect keys to remove, + // - per (hot,cold) α VALUE (not shares) with fallback to raw share if pool uninitialized, + // - track hotkeys to clear pool totals. + let mut keys_to_remove: Vec<(T::AccountId, T::AccountId)> = Vec::new(); + let mut hotkeys_seen: Vec = Vec::new(); + let mut stakers: Vec<(T::AccountId, T::AccountId, u128)> = Vec::new(); + let mut total_alpha_value_u128: u128 = 0; + + for ((hot, cold, this_netuid), share_u64f64) in Alpha::::iter() { + if this_netuid != netuid { + continue; + } + + keys_to_remove.push((hot.clone(), cold.clone())); + if !hotkeys_seen.contains(&hot) { + hotkeys_seen.push(hot.clone()); + } + + // Primary: actual α value via share pool. + let pool = Self::get_alpha_share_pool(hot.clone(), netuid); + let actual_val_u64 = pool.try_get_value(&cold).unwrap_or(0); + + // Fallback: if pool uninitialized, treat raw Alpha share as value. + let val_u64 = if actual_val_u64 == 0 { + share_u64f64.saturating_to_num::() + } else { + actual_val_u64 + }; + + if val_u64 > 0 { + let val_u128 = val_u64 as u128; + total_alpha_value_u128 = total_alpha_value_u128.saturating_add(val_u128); + stakers.push((hot, cold, val_u128)); + } + } + + // 5) Determine the TAO pot and pre-adjust accounting to avoid double counting. + let pot_tao: TaoCurrency = SubnetTAO::::get(netuid); + let pot_u64: u64 = pot_tao.into(); + + if pot_u64 > 0 { + // Remove TAO from dissolving subnet BEFORE restaking to ROOT to keep TotalStake consistent. + SubnetTAO::::remove(netuid); + TotalStake::::mutate(|total| *total = total.saturating_sub(pot_tao)); + } + + // 6) Pro‑rata distribution of the pot by α value (largest‑remainder). + if pot_u64 > 0 && total_alpha_value_u128 > 0 && !stakers.is_empty() { + struct Portion { + hot: A, + cold: C, + share: u64, // TAO to restake on ROOT + rem: u128, // remainder for largest‑remainder method + } + + let pot_u128: u128 = pot_u64 as u128; + let mut portions: Vec> = Vec::with_capacity(stakers.len()); + let mut distributed: u128 = 0; + + for (hot, cold, alpha_val) in &stakers { + let prod: u128 = pot_u128.saturating_mul(*alpha_val); + let share_u128: u128 = prod.checked_div(total_alpha_value_u128).unwrap_or_default(); + let share_u64: u64 = share_u128.min(u128::from(u64::MAX)) as u64; + distributed = distributed.saturating_add(u128::from(share_u64)); + + let rem: u128 = prod.checked_rem(total_alpha_value_u128).unwrap_or_default(); + portions.push(Portion { + hot: hot.clone(), + cold: cold.clone(), + share: share_u64, + rem, + }); + } + + let leftover: u128 = pot_u128.saturating_sub(distributed); + if leftover > 0 { + portions.sort_by(|a, b| b.rem.cmp(&a.rem)); + let give: usize = core::cmp::min(leftover, portions.len() as u128) as usize; + for p in portions.iter_mut().take(give) { + p.share = p.share.saturating_add(1); + } + } + + // Restake each portion into ROOT (stable 1:1), no price limit required. + let root_netuid = NetUid::ROOT; + for p in portions { + if p.share > 0 { + Self::stake_into_subnet( + &p.hot, + &p.cold, + root_netuid, + TaoCurrency::from(p.share), + TaoCurrency::from(0), + false, + )?; + } + } + } + + // 7) Destroy all α-in/α-out state for this subnet. + // 7.a) Remove every (hot, cold, netuid) α entry. + for (hot, cold) in keys_to_remove { + Alpha::::remove((hot, cold, netuid)); + } + // 7.b) Clear share‑pool totals for each hotkey on this subnet. + for hot in hotkeys_seen { + TotalHotkeyAlpha::::remove(&hot, netuid); + TotalHotkeyShares::::remove(&hot, netuid); + } + // 7.c) Remove α‑in/α‑out counters (fully destroyed). + SubnetAlphaIn::::remove(netuid); + SubnetAlphaInProvided::::remove(netuid); + SubnetAlphaOut::::remove(netuid); + + // 8) Refund remaining lock to subnet owner: + // refund = max(0, lock_cost(τ) − owner_received_emission_in_τ). + let refund: TaoCurrency = lock_cost.saturating_sub(owner_emission_tau); + + // Clear the locked balance on the subnet. + Self::set_subnet_locked_balance(netuid, TaoCurrency::ZERO); + + if !refund.is_zero() { + // Add back to owner’s coldkey free balance (expects runtime Balance u64). + Self::add_balance_to_coldkey_account(&owner_coldkey, refund.to_u64()); + } + + Ok(()) + } } diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index a0b39e151f..f29357c741 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -34,6 +34,7 @@ pub trait SwapHandler { alpha_delta: AlphaCurrency, ); fn is_user_liquidity_enabled(netuid: NetUid) -> bool; + fn liquidate_all_liquidity_providers(netuid: NetUid) -> DispatchResult; } #[derive(Debug, PartialEq)] diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index deebabd673..ff715bede8 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -5,7 +5,7 @@ use frame_support::storage::{TransactionOutcome, transactional}; use frame_support::{ensure, pallet_prelude::DispatchError, traits::Get}; use safe_math::*; use sp_arithmetic::helpers_128bit; -use sp_runtime::traits::AccountIdConversion; +use sp_runtime::{DispatchResult, traits::AccountIdConversion}; use substrate_fixed::types::{I64F64, U64F64, U96F32}; use subtensor_runtime_common::{ AlphaCurrency, BalanceOps, Currency, NetUid, SubnetInfo, TaoCurrency, @@ -1212,6 +1212,137 @@ impl Pallet { pub fn protocol_account_id() -> T::AccountId { T::ProtocolId::get().into_account_truncating() } + /// Liquidate (force-close) all LPs for `netuid`, **refund** providers, and reset all swap state. + /// + /// - **V3 path** (mechanism==1 && SwapV3Initialized): + /// * Remove **all** positions (user + protocol) via `do_remove_liquidity`. + /// * **Refund** each owner: TAO = `rm.tao + rm.fee_tao`, ALPHA = `rm.alpha + rm.fee_alpha`, + /// using `T::BalanceOps::{deposit_tao, deposit_alpha}`. + /// * Clear ActiveTickIndexManager entries, ticks, fee globals, price, tick, liquidity, + /// init flag, bitmap words, fee rate knob, and user LP flag. + /// + /// - **V2 / non‑V3 path**: + /// * No per‑position records exist; still defensively clear the same V3 storages + /// (safe no‑ops) so the subnet leaves **no swap residue**. + pub fn do_liquidate_all_liquidity_providers(netuid: NetUid) -> DispatchResult { + let mechid = T::SubnetInfo::mechanism(netuid.into()); + let v3_initialized = SwapV3Initialized::::get(netuid); + let user_lp_enabled = + >::is_user_liquidity_enabled(netuid); + + let is_v3_mode = mechid == 1 && v3_initialized; + + if is_v3_mode { + // -------- V3: close every position, REFUND owners, then clear all V3 state -------- + + // 1) Snapshot all (owner, position_id) under this netuid to avoid iterator aliasing. + let mut to_close: sp_std::vec::Vec<(T::AccountId, PositionId)> = + sp_std::vec::Vec::new(); + for ((n, owner, pos_id), _pos) in Positions::::iter() { + if n == netuid { + to_close.push((owner, pos_id)); + } + } + + let protocol_account = Self::protocol_account_id(); + + // 2) Remove all positions (user + protocol) and REFUND both legs to the owner. + for (owner, pos_id) in to_close.into_iter() { + let rm = Self::do_remove_liquidity(netuid, &owner, pos_id)?; + + // Refund TAO: principal + accrued TAO fees. + let tao_refund = rm.tao.saturating_add(rm.fee_tao); + if tao_refund > TaoCurrency::ZERO { + T::BalanceOps::increase_balance(&owner, tao_refund); + } + + // Refund ALPHA: principal + accrued ALPHA fees. + let alpha_refund = rm.alpha.saturating_add(rm.fee_alpha); + if !alpha_refund.is_zero() { + // Credit ALPHA back to the provider on (coldkey=owner, hotkey=owner). + T::BalanceOps::increase_stake(&owner, &owner, netuid.into(), alpha_refund)?; + } + + // Mirror `remove_liquidity`: update **user-provided** reserves by principal only. + // Skip for protocol-owned liquidity which never contributed to provided reserves. + if owner != protocol_account { + T::BalanceOps::decrease_provided_tao_reserve(netuid.into(), rm.tao); + T::BalanceOps::decrease_provided_alpha_reserve(netuid.into(), rm.alpha); + } + } + + // 3) Clear active tick index set by walking ticks we are about to clear. + let active_ticks: sp_std::vec::Vec = + Ticks::::iter_prefix(netuid).map(|(ti, _)| ti).collect(); + for ti in active_ticks { + ActiveTickIndexManager::::remove(netuid, ti); + } + + // 4) Clear storage: + // Positions (StorageNMap) – prefix is **(netuid,)** not just netuid. + let _ = Positions::::clear_prefix((netuid,), u32::MAX, None); + + // Ticks (DoubleMap) – OK to pass netuid as first key. + let _ = Ticks::::clear_prefix(netuid, u32::MAX, None); + + // Fee globals, price/tick/liquidity, v3 init flag. + FeeGlobalTao::::remove(netuid); + FeeGlobalAlpha::::remove(netuid); + CurrentLiquidity::::remove(netuid); + CurrentTick::::remove(netuid); + AlphaSqrtPrice::::remove(netuid); + SwapV3Initialized::::remove(netuid); + + // Active tick bitmap words (StorageNMap) – prefix is **(netuid,)**. + let _ = TickIndexBitmapWords::::clear_prefix((netuid,), u32::MAX, None); + + // Remove knobs (safe on deregistration). + FeeRate::::remove(netuid); + EnabledUserLiquidity::::remove(netuid); + + log::debug!( + "liquidate_all_liquidity_providers: netuid={:?}, mode=V3, user_lp_enabled={}, v3_state_cleared + refunds", + netuid, + user_lp_enabled + ); + + return Ok(()); + } + + // -------- V2 / non‑V3: no positions to close; still nuke any V3 residues -------- + + // Positions (StorageNMap) – prefix is (netuid,) + let _ = Positions::::clear_prefix((netuid,), u32::MAX, None); + + // Active ticks set via ticks present (if any) + let active_ticks: sp_std::vec::Vec = + Ticks::::iter_prefix(netuid).map(|(ti, _)| ti).collect(); + for ti in active_ticks { + ActiveTickIndexManager::::remove(netuid, ti); + } + + let _ = Ticks::::clear_prefix(netuid, u32::MAX, None); + + FeeGlobalTao::::remove(netuid); + FeeGlobalAlpha::::remove(netuid); + CurrentLiquidity::::remove(netuid); + CurrentTick::::remove(netuid); + AlphaSqrtPrice::::remove(netuid); + SwapV3Initialized::::remove(netuid); + + let _ = TickIndexBitmapWords::::clear_prefix((netuid,), u32::MAX, None); + + FeeRate::::remove(netuid); + EnabledUserLiquidity::::remove(netuid); + + log::debug!( + "liquidate_all_liquidity_providers: netuid={:?}, mode=V2-or-nonV3, user_lp_enabled={}, state_cleared", + netuid, + user_lp_enabled + ); + + Ok(()) + } } impl SwapHandler for Pallet { @@ -1297,6 +1428,9 @@ impl SwapHandler for Pallet { fn is_user_liquidity_enabled(netuid: NetUid) -> bool { EnabledUserLiquidity::::get(netuid) } + fn liquidate_all_liquidity_providers(netuid: NetUid) -> DispatchResult { + Self::do_liquidate_all_liquidity_providers(netuid) + } } #[derive(Debug, PartialEq)] diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index 4c3a890c9b..6b5204f536 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -1910,3 +1910,369 @@ fn test_less_price_movement() { }); }); } + +/// V3 path: protocol + user positions exist, fees accrued, everything must be removed. +#[test] +fn test_liquidate_v3_removes_positions_ticks_and_state() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + + // Initialize V3 (creates protocol position, ticks, price, liquidity) + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + assert!(SwapV3Initialized::::get(netuid)); + + // Enable user LP (mock usually enables for 0..=100, but be explicit and consistent) + assert_ok!(Swap::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid.into(), + true + )); + + // Add a user position across the full range to ensure ticks/bitmap are populated. + let min_price = tick_to_price(TickIndex::MIN); + let max_price = tick_to_price(TickIndex::MAX); + let tick_low = price_to_tick(min_price); + let tick_high = price_to_tick(max_price); + let liquidity = 2_000_000_000_u64; + + let (_pos_id, _tao, _alpha) = Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + liquidity, + ) + .expect("add liquidity"); + + // Accrue some global fees so we can verify fee storage is cleared later. + let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); + assert_ok!(Pallet::::do_swap( + netuid, + OrderType::Buy, + 1_000_000, + sqrt_limit_price, + false, + false + )); + + // Sanity: protocol & user positions exist, ticks exist, liquidity > 0 + let protocol_id = Pallet::::protocol_account_id(); + let prot_positions = + Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); + assert!(!prot_positions.is_empty()); + + let user_positions = Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .collect::>(); + assert_eq!(user_positions.len(), 1); + + assert!(Ticks::::get(netuid, TickIndex::MIN).is_some()); + assert!(Ticks::::get(netuid, TickIndex::MAX).is_some()); + assert!(CurrentLiquidity::::get(netuid) > 0); + + // There should be some bitmap words (active ticks) after adding a position. + let had_bitmap_words = TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_some(); + assert!(had_bitmap_words); + + // ACT: Liquidate & reset swap state + assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + + // ASSERT: positions cleared (both user and protocol) + assert_eq!( + Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), + 0 + ); + let prot_positions_after = + Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); + assert!(prot_positions_after.is_empty()); + let user_positions_after = + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .collect::>(); + assert!(user_positions_after.is_empty()); + + // ASSERT: ticks cleared + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!(Ticks::::get(netuid, TickIndex::MIN).is_none()); + assert!(Ticks::::get(netuid, TickIndex::MAX).is_none()); + + // ASSERT: fee globals cleared + assert!(!FeeGlobalTao::::contains_key(netuid)); + assert!(!FeeGlobalAlpha::::contains_key(netuid)); + + // ASSERT: price/tick/liquidity flags cleared + assert!(!AlphaSqrtPrice::::contains_key(netuid)); + assert!(!CurrentTick::::contains_key(netuid)); + assert!(!CurrentLiquidity::::contains_key(netuid)); + assert!(!SwapV3Initialized::::contains_key(netuid)); + + // ASSERT: active tick bitmap cleared + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + + // ASSERT: knobs removed on dereg + assert!(!FeeRate::::contains_key(netuid)); + assert!(!EnabledUserLiquidity::::contains_key(netuid)); + }); +} + +/// V3 path with user liquidity disabled at teardown: must still remove all positions and clear state. +#[test] +fn test_liquidate_v3_with_user_liquidity_disabled() { + new_test_ext().execute_with(|| { + // Pick a netuid the mock treats as "disabled" by default (per your comment >100), + // then explicitly walk through enable -> add -> disable -> liquidate. + let netuid = NetUid::from(101); + + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + assert!(SwapV3Initialized::::get(netuid)); + + // Enable temporarily to add a user position + assert_ok!(Swap::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid.into(), + true + )); + + let min_price = tick_to_price(TickIndex::MIN); + let max_price = tick_to_price(TickIndex::MAX); + let tick_low = price_to_tick(min_price); + let tick_high = price_to_tick(max_price); + let liquidity = 1_000_000_000_u64; + + let (_pos_id, _tao, _alpha) = Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + liquidity, + ) + .expect("add liquidity"); + + // Disable user LP *before* liquidation to validate that removal ignores this flag. + assert_ok!(Swap::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid.into(), + false + )); + + // ACT + assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + + // ASSERT: positions & ticks gone, state reset + assert_eq!( + Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), + 0 + ); + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + assert!(!SwapV3Initialized::::contains_key(netuid)); + assert!(!AlphaSqrtPrice::::contains_key(netuid)); + assert!(!CurrentTick::::contains_key(netuid)); + assert!(!CurrentLiquidity::::contains_key(netuid)); + assert!(!FeeGlobalTao::::contains_key(netuid)); + assert!(!FeeGlobalAlpha::::contains_key(netuid)); + + // `EnabledUserLiquidity` is removed by liquidation. + assert!(!EnabledUserLiquidity::::contains_key(netuid)); + }); +} + +/// Non‑V3 path: V3 not initialized (no positions); function must still clear any residual storages and succeed. +#[test] +fn test_liquidate_non_v3_uninitialized_ok_and_clears() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(202); + + // Sanity: V3 is not initialized + assert!(!SwapV3Initialized::::get(netuid)); + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + + // ACT + assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + + // ASSERT: Defensive clears leave no residues and do not panic + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + + // All single-key maps should not have the key after liquidation + assert!(!FeeGlobalTao::::contains_key(netuid)); + assert!(!FeeGlobalAlpha::::contains_key(netuid)); + assert!(!CurrentLiquidity::::contains_key(netuid)); + assert!(!CurrentTick::::contains_key(netuid)); + assert!(!AlphaSqrtPrice::::contains_key(netuid)); + assert!(!SwapV3Initialized::::contains_key(netuid)); + assert!(!FeeRate::::contains_key(netuid)); + assert!(!EnabledUserLiquidity::::contains_key(netuid)); + }); +} + +/// Idempotency: calling liquidation twice is safe (both V3 and non‑V3 flavors). +#[test] +fn test_liquidate_idempotent() { + // V3 flavor + new_test_ext().execute_with(|| { + let netuid = NetUid::from(7); + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Add a small user position + assert_ok!(Swap::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid.into(), + true + )); + let tick_low = price_to_tick(0.2); + let tick_high = price_to_tick(0.3); + assert_ok!(Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + 123_456_789 + )); + + // 1st liquidation + assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + // 2nd liquidation (no state left) — must still succeed + assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + + // State remains empty + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + assert!(!SwapV3Initialized::::contains_key(netuid)); + }); + + // Non‑V3 flavor + new_test_ext().execute_with(|| { + let netuid = NetUid::from(8); + + // Never initialize V3 + assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + assert!(!SwapV3Initialized::::contains_key(netuid)); + }); +} + +#[test] +fn liquidate_v3_refunds_user_funds_and_clears_state() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + + // Enable V3 path & initialize price/ticks (also creates a protocol position). + assert_ok!(Pallet::::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid, + true + )); + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Use distinct cold/hot to demonstrate alpha refund goes to (owner, owner). + let cold = OK_COLDKEY_ACCOUNT_ID; + let hot = OK_HOTKEY_ACCOUNT_ID; + + // Tight in‑range band around current tick. + let ct = CurrentTick::::get(netuid); + let tick_low = ct.saturating_sub(10); + let tick_high = ct.saturating_add(10); + let liquidity: u64 = 1_000_000; + + // Snapshot balances BEFORE. + let tao_before = ::BalanceOps::tao_balance(&cold); + let alpha_before_hot = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); + let alpha_before_owner = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let alpha_before_total = alpha_before_hot + alpha_before_owner; + + // Create the user position (storage & v3 state only; no balances moved yet). + let (_pos_id, need_tao, need_alpha) = + Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) + .expect("add liquidity"); + + // Mirror extrinsic bookkeeping: withdraw funds & bump provided‑reserve counters. + let tao_taken = ::BalanceOps::decrease_balance(&cold, need_tao.into()) + .expect("decrease TAO"); + let alpha_taken = ::BalanceOps::decrease_stake( + &cold, + &hot, + netuid.into(), + need_alpha.into(), + ) + .expect("decrease ALPHA"); + ::BalanceOps::increase_provided_tao_reserve(netuid.into(), tao_taken); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), alpha_taken); + + // Liquidate everything on the subnet. + assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + + // Expect balances restored to BEFORE snapshots (no swaps ran -> zero fees). + // TAO: we withdrew 'need_tao' above and liquidation refunded it, so we should be back to 'tao_before'. + let tao_after = ::BalanceOps::tao_balance(&cold); + assert_eq!(tao_after, tao_before, "TAO principal must be refunded"); + + // ALPHA: refund is credited to (coldkey=cold, hotkey=cold). Compare totals across both ledgers. + let alpha_after_hot = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); + let alpha_after_owner = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let alpha_after_total = alpha_after_hot + alpha_after_owner; + assert_eq!( + alpha_after_total, alpha_before_total, + "ALPHA principal must be refunded to the account (may be credited to (owner, owner))" + ); + + // User position(s) are gone and all V3 state cleared. + assert_eq!(Pallet::::count_positions(netuid, &cold), 0); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!(!SwapV3Initialized::::contains_key(netuid)); + }); +} From 2ae2eb65185bb09ea0c46c5990560b96d3f5f8b4 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:36:46 -0700 Subject: [PATCH 46/97] add stake to balance --- pallets/subtensor/src/staking/remove_stake.rs | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 5249d6782e..0df38eb71c 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -452,7 +452,6 @@ impl Pallet { let lock_cost: TaoCurrency = Self::get_subnet_locked_balance(netuid); // 3) Compute owner's received emission in TAO at current price. - // // Emission:: is Vec. We: // - sum emitted α, // - apply owner fraction to get owner α, @@ -480,7 +479,7 @@ impl Pallet { .saturating_mul(cur_price) .floor() .saturating_to_num::(); - let owner_emission_tau: TaoCurrency = owner_emission_tao_u64.into(); + let owner_emission_tao: TaoCurrency = owner_emission_tao_u64.into(); // 4) Enumerate all α entries on this subnet to build distribution weights and cleanup lists. // - collect keys to remove, @@ -524,17 +523,17 @@ impl Pallet { let pot_u64: u64 = pot_tao.into(); if pot_u64 > 0 { - // Remove TAO from dissolving subnet BEFORE restaking to ROOT to keep TotalStake consistent. SubnetTAO::::remove(netuid); TotalStake::::mutate(|total| *total = total.saturating_sub(pot_tao)); } - // 6) Pro‑rata distribution of the pot by α value (largest‑remainder). + // 6) Pro‑rata distribution of the pot by α value (largest‑remainder), + // **credited directly to each staker's COLDKEY free balance**. if pot_u64 > 0 && total_alpha_value_u128 > 0 && !stakers.is_empty() { struct Portion { - hot: A, + _hot: A, cold: C, - share: u64, // TAO to restake on ROOT + share: u64, // TAO to credit to coldkey balance rem: u128, // remainder for largest‑remainder method } @@ -550,7 +549,7 @@ impl Pallet { let rem: u128 = prod.checked_rem(total_alpha_value_u128).unwrap_or_default(); portions.push(Portion { - hot: hot.clone(), + _hot: hot.clone(), cold: cold.clone(), share: share_u64, rem, @@ -566,18 +565,10 @@ impl Pallet { } } - // Restake each portion into ROOT (stable 1:1), no price limit required. - let root_netuid = NetUid::ROOT; + // Credit each share directly to coldkey free balance. for p in portions { if p.share > 0 { - Self::stake_into_subnet( - &p.hot, - &p.cold, - root_netuid, - TaoCurrency::from(p.share), - TaoCurrency::from(0), - false, - )?; + Self::add_balance_to_coldkey_account(&p.cold, p.share); } } } @@ -599,13 +590,12 @@ impl Pallet { // 8) Refund remaining lock to subnet owner: // refund = max(0, lock_cost(τ) − owner_received_emission_in_τ). - let refund: TaoCurrency = lock_cost.saturating_sub(owner_emission_tau); + let refund: TaoCurrency = lock_cost.saturating_sub(owner_emission_tao); // Clear the locked balance on the subnet. Self::set_subnet_locked_balance(netuid, TaoCurrency::ZERO); if !refund.is_zero() { - // Add back to owner’s coldkey free balance (expects runtime Balance u64). Self::add_balance_to_coldkey_account(&owner_coldkey, refund.to_u64()); } From 63bad12cb1e2197ca772add5c663994b25f667f2 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:39:06 -0700 Subject: [PATCH 47/97] update tests --- pallets/subtensor/src/tests/networks.rs | 190 +++++++++++++----------- 1 file changed, 106 insertions(+), 84 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 1b216fdfca..baa3910fa6 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -95,23 +95,24 @@ fn dissolve_single_alpha_out_staker_gets_all_tao() { let (s_hot, s_cold) = (U256::from(100), U256::from(200)); Alpha::::insert((s_hot, s_cold, net), U64F64::from_num(5_000u128)); - SubnetTAO::::insert(net, TaoCurrency::from(99_999)); + // Entire TAO pot should be paid to staker's cold-key + let pot: u64 = 99_999; + SubnetTAO::::insert(net, TaoCurrency::from(pot)); SubtensorModule::set_subnet_locked_balance(net, 0.into()); - // α on ROOT before - let root = NetUid::ROOT; - let alpha_before_root = - Alpha::::get((s_hot, s_cold, root)).saturating_to_num::(); + // Cold-key balance before + let before = SubtensorModule::get_coldkey_balance(&s_cold); - // 3. Dissolve + // Dissolve assert_ok!(SubtensorModule::do_dissolve_network(net)); - // 4. Entire TAO pot should now be α on root - let alpha_after_root = Alpha::::get((s_hot, s_cold, root)).saturating_to_num::(); - assert_eq!(alpha_after_root, alpha_before_root + 99_999); + // Cold-key received full pot + let after = SubtensorModule::get_coldkey_balance(&s_cold); + assert_eq!(after, before + pot); // No α entries left for dissolved subnet assert!(Alpha::::iter().all(|((_h, _c, n), _)| n != net)); + assert!(!SubnetTAO::::contains_key(net)); }); } @@ -129,33 +130,63 @@ fn dissolve_two_stakers_pro_rata_distribution() { Alpha::::insert((s1_hot, s1_cold, net), U64F64::from_num(a1)); Alpha::::insert((s2_hot, s2_cold, net), U64F64::from_num(a2)); - SubnetTAO::::insert(net, TaoCurrency::from(10_000)); - SubtensorModule::set_subnet_locked_balance(net, 5_000.into()); + let pot: u64 = 10_000; + SubnetTAO::::insert(net, TaoCurrency::from(pot)); + SubtensorModule::set_subnet_locked_balance(net, 5_000.into()); // owner refund path present but emission = 0 - // α on ROOT before - let root = NetUid::ROOT; - let a1_root_before = Alpha::::get((s1_hot, s1_cold, root)).saturating_to_num::(); - let a2_root_before = Alpha::::get((s2_hot, s2_cold, root)).saturating_to_num::(); - - // Run dissolve - assert_ok!(SubtensorModule::do_dissolve_network(net)); + // Cold-key balances before + let s1_before = SubtensorModule::get_coldkey_balance(&s1_cold); + let s2_before = SubtensorModule::get_coldkey_balance(&s2_cold); + let owner_before = SubtensorModule::get_coldkey_balance(&oc); - // Expected TAO shares + // Expected τ shares with largest remainder let total = a1 + a2; - let share1_tao: u64 = (10_000u128 * a1 / total) as u64; - let share2_tao: u64 = (10_000u128 * a2 / total) as u64; + let prod1 = (a1 as u128) * (pot as u128); + let prod2 = (a2 as u128) * (pot as u128); + let share1 = (prod1 / total) as u64; + let share2 = (prod2 / total) as u64; + let mut distributed = share1 + share2; + let mut rem = [(s1_cold, prod1 % total), (s2_cold, prod2 % total)]; + if distributed < pot { + rem.sort_by_key(|&(_c, r)| core::cmp::Reverse(r)); + let leftover = pot - distributed; + for i in 0..(leftover as usize) { + if rem[i].0 == s1_cold { + distributed += 1; + } else { + distributed += 1; + } + } + } + // Recompute exact expected shares using the same logic + let mut expected1 = share1; + let mut expected2 = share2; + if share1 + share2 < pot { + rem.sort_by_key(|&(_c, r)| core::cmp::Reverse(r)); + if rem[0].0 == s1_cold { + expected1 += 1; + } else { + expected2 += 1; + } + } - // α on root should have increased by those shares - let a1_root_after = Alpha::::get((s1_hot, s1_cold, root)).saturating_to_num::(); - let a2_root_after = Alpha::::get((s2_hot, s2_cold, root)).saturating_to_num::(); + // Dissolve + assert_ok!(SubtensorModule::do_dissolve_network(net)); - assert_eq!(a1_root_after, a1_root_before + share1_tao); - assert_eq!(a2_root_after, a2_root_before + share2_tao); + // Cold-keys received their τ shares + assert_eq!( + SubtensorModule::get_coldkey_balance(&s1_cold), + s1_before + expected1 + ); + assert_eq!( + SubtensorModule::get_coldkey_balance(&s2_cold), + s2_before + expected2 + ); - // owner refund (5 000 τ) still to cold-key + // Owner refunded lock (no emission) assert_eq!( SubtensorModule::get_coldkey_balance(&oc), - SubtensorModule::get_coldkey_balance(&oc) // unchanged; refund already applied internally + owner_before + 5_000 ); // α entries for dissolved subnet gone @@ -432,28 +463,25 @@ fn dissolve_rounding_remainder_distribution() { SubnetTAO::::insert(net, TaoCurrency::from(1)); // TAO pot = 1 SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(0)); - // 2. α on ROOT before - let root = NetUid::ROOT; - let a1_before = Alpha::::get((s1h, s1c, root)).saturating_to_num::(); - let a2_before = Alpha::::get((s2h, s2c, root)).saturating_to_num::(); + // Cold-key balances before + let c1_before = SubtensorModule::get_coldkey_balance(&s1c); + let c2_before = SubtensorModule::get_coldkey_balance(&s2c); // 3. Run full dissolve flow assert_ok!(SubtensorModule::do_dissolve_network(net)); - // 4. s1 (larger remainder) should now have +1 α on ROOT - let a1_after = Alpha::::get((s1h, s1c, root)).saturating_to_num::(); - let a2_after = Alpha::::get((s2h, s2c, root)).saturating_to_num::(); + // 4. s1 (larger remainder) should get +1 τ on cold-key + let c1_after = SubtensorModule::get_coldkey_balance(&s1c); + let c2_after = SubtensorModule::get_coldkey_balance(&s2c); - assert_eq!(a1_after, a1_before + 1); - assert_eq!(a2_after, a2_before); + assert_eq!(c1_after, c1_before + 1); + assert_eq!(c2_after, c2_before); - // α records for subnet gone + // α records for subnet gone; TAO key gone assert!(Alpha::::iter().all(|((_h, _c, n), _)| n != net)); - // TAO storage key gone assert!(!SubnetTAO::::contains_key(net)); }); } - #[test] fn destroy_alpha_out_multiple_stakers_pro_rata() { new_test_ext(0).execute_with(|| { @@ -500,40 +528,48 @@ fn destroy_alpha_out_multiple_stakers_pro_rata() { SubnetTAO::::insert(netuid, TaoCurrency::from(tao_pot)); SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(5_000)); - // 6. Balances & α on the *root* network *before* - let root = NetUid::ROOT; - let bal1_before = SubtensorModule::get_coldkey_balance(&c1); - let bal2_before = SubtensorModule::get_coldkey_balance(&c2); + // 6. Balances before + let c1_before = SubtensorModule::get_coldkey_balance(&c1); + let c2_before = SubtensorModule::get_coldkey_balance(&c2); let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); - let alpha1_before_root: u64 = Alpha::::get((h1, c1, root)).saturating_to_num(); - let alpha2_before_root: u64 = Alpha::::get((h2, c2, root)).saturating_to_num(); - - // 7. Run the burn-and-restake logic + // 7. Run the (now credit-to-coldkey) logic assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); - // 8. Expected TAO shares - let share1_tao: u64 = (tao_pot as u128 * a1 / atotal) as u64; - let share2_tao: u64 = tao_pot - share1_tao; + // 8. Expected τ shares via largest remainder + let prod1 = (tao_pot as u128) * a1; + let prod2 = (tao_pot as u128) * a2; + let mut s1_share = (prod1 / atotal) as u64; + let mut s2_share = (prod2 / atotal) as u64; + let distributed = s1_share + s2_share; + if distributed < tao_pot { + // Assign leftover to larger remainder + let r1 = prod1 % atotal; + let r2 = prod2 % atotal; + if r1 >= r2 { + s1_share += 1; + } else { + s2_share += 1; + } + } - // 9. Assert cold-key balances unchanged (stakers) - assert_eq!(SubtensorModule::get_coldkey_balance(&c1), bal1_before); - assert_eq!(SubtensorModule::get_coldkey_balance(&c2), bal2_before); + // 9. Cold-key balances must have increased accordingly + assert_eq!( + SubtensorModule::get_coldkey_balance(&c1), + c1_before + s1_share + ); + assert_eq!( + SubtensorModule::get_coldkey_balance(&c2), + c2_before + s2_share + ); - // 10. Assert owner refund (5 000 τ) still hits cold-key + // 10. Owner refund (5 000 τ) to cold-key (no emission) assert_eq!( SubtensorModule::get_coldkey_balance(&owner_cold), owner_before + 5_000 ); - // 11. Assert α on ROOT increased by exactly the TAO restaked - let alpha1_after_root: u64 = Alpha::::get((h1, c1, root)).saturating_to_num(); - let alpha2_after_root: u64 = Alpha::::get((h2, c2, root)).saturating_to_num(); - - assert_eq!(alpha1_after_root, alpha1_before_root + share1_tao); - assert_eq!(alpha2_after_root, alpha2_before_root + share2_tao); - - // 12. No α entries left for the dissolved subnet + // 11. α entries cleared for the subnet assert!(!Alpha::::contains_key((h1, c1, netuid))); assert!(!Alpha::::contains_key((h2, c2, netuid))); }); @@ -607,17 +643,14 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { ); SubnetOwnerCut::::put(32_768u16); // ~ 0.5 in fixed-point - // ── 4) balances & α on ROOT before ────────────────────────────────── - let root = NetUid::ROOT; + // ── 4) balances before ────────────────────────────────────────────── let mut bal_before = [0u64; N]; - let mut alpha_before_root = [0u64; N]; for i in 0..N { bal_before[i] = SubtensorModule::get_coldkey_balance(&cold[i]); - alpha_before_root[i] = Alpha::::get((hot[i], cold[i], root)).saturating_to_num(); } let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); - // ── 5) expected TAO share per pallet algorithm (incl. remainder) ──── + // ── 5) expected τ share per pallet algorithm (incl. remainder) ───── let mut share = [0u64; N]; let mut rem = [0u128; N]; let mut paid: u128 = 0; @@ -651,27 +684,16 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { .saturating_to_num::(); let expected_refund: u64 = lock.saturating_sub(owner_emission_tao_u64); - // ── 6) run burn-and-restake ──────────────────────────────────────── + // ── 6) run distribution (credits τ to coldkeys, wipes α state) ───── assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); // ── 7) post checks ────────────────────────────────────────────────── for i in 0..N { - // cold-key balances unchanged + // cold-key balances increased by expected τ share assert_eq!( SubtensorModule::get_coldkey_balance(&cold[i]), - bal_before[i], - "staker {} cold-key balance changed", - i - ); - - // α added on ROOT == TAO share - let alpha_after_root: u64 = - Alpha::::get((hot[i], cold[i], root)).saturating_to_num(); - - assert_eq!( - alpha_after_root, - alpha_before_root[i] + share[i], - "staker {} incorrect α restaked", + bal_before[i] + share[i], + "staker {} cold-key balance changed unexpectedly", i ); } @@ -682,7 +704,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { owner_before + expected_refund ); - // α cleared for dissolved subnet + // α cleared for dissolved subnet & related counters reset assert!(Alpha::::iter().all(|((_h, _c, n), _)| n != netuid)); assert_eq!(SubnetAlphaIn::::get(netuid), 0.into()); assert_eq!(SubnetAlphaOut::::get(netuid), 0.into()); From 3c53b3002d1bceefc9bee0d91504f724b322cbe9 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:39:43 -0700 Subject: [PATCH 48/97] use the actual hotkey --- common/src/lib.rs | 3 +- pallets/subtensor/src/lib.rs | 3 + pallets/swap/src/mock.rs | 6 +- pallets/swap/src/pallet/impls.rs | 206 ++++++++++++++++++++++++++----- 4 files changed, 187 insertions(+), 31 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index a3882a88fc..ac17361879 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -9,7 +9,7 @@ use runtime_common::prod_or_fast; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{ - MultiSignature, + MultiSignature, Vec, traits::{IdentifyAccount, Verify}, }; use subtensor_macros::freeze_struct; @@ -174,6 +174,7 @@ pub trait SubnetInfo { fn exists(netuid: NetUid) -> bool; fn mechanism(netuid: NetUid) -> u16; fn is_owner(account_id: &AccountId, netuid: NetUid) -> bool; + fn get_owned_hotkeys(coldkey: &AccountId) -> Vec; } pub trait BalanceOps { diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b45fcaa2b1..173ccd3aff 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2593,6 +2593,9 @@ impl> fn is_owner(account_id: &T::AccountId, netuid: NetUid) -> bool { SubnetOwner::::get(netuid) == *account_id } + fn get_owned_hotkeys(coldkey: &T::AccountId) -> Vec { + OwnedHotkeys::::get(coldkey) + } } impl> diff --git a/pallets/swap/src/mock.rs b/pallets/swap/src/mock.rs index 78a8f925c8..ec22c12472 100644 --- a/pallets/swap/src/mock.rs +++ b/pallets/swap/src/mock.rs @@ -11,7 +11,7 @@ use frame_support::{ use frame_system::{self as system}; use sp_core::H256; use sp_runtime::{ - BuildStorage, + BuildStorage, Vec, traits::{BlakeTwo256, IdentityLookup}, }; use subtensor_runtime_common::{AlphaCurrency, BalanceOps, NetUid, SubnetInfo, TaoCurrency}; @@ -115,6 +115,10 @@ impl SubnetInfo for MockLiquidityProvider { fn is_owner(account_id: &AccountId, _netuid: NetUid) -> bool { *account_id != NOT_SUBNET_OWNER } + + fn get_owned_hotkeys(_coldkey: &AccountId) -> Vec { + Vec::::new() + } } pub struct MockBalanceOps; diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index ff715bede8..c40579b345 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1212,73 +1212,221 @@ impl Pallet { pub fn protocol_account_id() -> T::AccountId { T::ProtocolId::get().into_account_truncating() } - /// Liquidate (force-close) all LPs for `netuid`, **refund** providers, and reset all swap state. + + /// Distribute `alpha_total` back to the coldkey's hotkeys for `netuid`. + /// - If the coldkey owns multiple hotkeys, split pro‑rata by current α stake on this subnet. + /// If all stakes are zero, split evenly. + /// - If no hotkeys exist, fall back to (coldkey, coldkey). + /// + pub fn refund_alpha_to_hotkeys( + netuid: NetUid, + coldkey: &T::AccountId, + alpha_total: AlphaCurrency, + ) { + if alpha_total.is_zero() { + return; + } + + // 1) Fetch owned hotkeys via SubnetInfo; no direct dependency on pallet_subtensor. + let mut hotkeys: sp_std::vec::Vec = T::SubnetInfo::get_owned_hotkeys(coldkey); + + // Fallback: if no hotkeys are currently owned, use coldkey as its own hotkey. + if hotkeys.is_empty() { + hotkeys.push(coldkey.clone()); + } + + // 2) Build weights based on current α stake on this subnet. + // If sum_weights == 0, we'll split evenly. + let weights: sp_std::vec::Vec = hotkeys + .iter() + .map(|hk| { + let bal = T::BalanceOps::alpha_balance(netuid, coldkey, hk); + bal.to_u64() as u128 + }) + .collect(); + + let sum_weights: u128 = weights.iter().copied().sum(); + let n: u128 = hotkeys.len() as u128; + + let total_alpha_u128: u128 = alpha_total.to_u64() as u128; + + // 3) Compute integer shares with remainder handling. + let mut shares: sp_std::vec::Vec<(T::AccountId, u64)> = + sp_std::vec::Vec::with_capacity(hotkeys.len()); + if sum_weights > 0 { + // Pro‑rata by weights. + let mut assigned: u128 = 0; + for (hk, w) in hotkeys.iter().cloned().zip(weights.iter().copied()) { + let part: u128 = (total_alpha_u128.saturating_mul(w)) / sum_weights; + shares.push((hk, part as u64)); + assigned = assigned.saturating_add(part); + } + let mut remainder: u64 = total_alpha_u128 + .saturating_sub(assigned) + .try_into() + .unwrap_or(0); + let len = shares.len(); + let mut i = 0usize; + while remainder > 0 && i < len { + shares[i].1 = shares[i].1.saturating_add(1); + remainder = remainder.saturating_sub(1); + i += 1; + } + } else { + // Even split. + let base: u64 = (total_alpha_u128 / n) as u64; + let mut remainder: u64 = (total_alpha_u128 % n) as u64; + for hk in hotkeys.into_iter() { + let add_one = if remainder > 0 { + remainder = remainder.saturating_sub(1); + 1 + } else { + 0 + }; + shares.push((hk, base.saturating_add(add_one))); + } + } + + // 4) Deposit to (coldkey, hotkey). On failure, collect leftover and retry on successes. + let mut leftover: u64 = 0; + let mut successes: sp_std::vec::Vec = sp_std::vec::Vec::new(); + + for (hk, amt_u64) in shares.iter() { + if *amt_u64 == 0 { + continue; + } + let amt: AlphaCurrency = (*amt_u64).into(); + match T::BalanceOps::increase_stake(coldkey, hk, netuid, amt) { + Ok(_) => successes.push(hk.clone()), + Err(e) => { + log::warn!( + "refund_alpha_to_hotkeys: increase_stake failed (cold={:?}, hot={:?}, netuid={:?}, amt={:?}): {:?}", + coldkey, + hk, + netuid, + amt_u64, + e + ); + leftover = leftover.saturating_add(*amt_u64); + } + } + } + + // 5) Retry: spread any leftover across the hotkeys that succeeded in step 4. + if leftover > 0 && !successes.is_empty() { + let count = successes.len() as u64; + let base = leftover / count; + let mut rem = leftover % count; + + for hk in successes.iter() { + let add: u64 = base.saturating_add(if rem > 0 { + rem -= 1; + 1 + } else { + 0 + }); + if add == 0 { + continue; + } + let _ = T::BalanceOps::increase_stake(coldkey, hk, netuid, add.into()); + } + leftover = 0; + } + + // 6) Final fallback: if for some reason every deposit failed, deposit to (coldkey, coldkey). + if leftover > 0 { + let _ = T::BalanceOps::increase_stake(coldkey, coldkey, netuid, leftover.into()); + } + } + + /// Liquidate (force-close) all LPs for `netuid`, refund providers, and reset all swap state. /// - /// - **V3 path** (mechanism==1 && SwapV3Initialized): + /// - **V3 path** (mechanism == 1 && SwapV3Initialized): /// * Remove **all** positions (user + protocol) via `do_remove_liquidity`. - /// * **Refund** each owner: TAO = `rm.tao + rm.fee_tao`, ALPHA = `rm.alpha + rm.fee_alpha`, - /// using `T::BalanceOps::{deposit_tao, deposit_alpha}`. + /// * **Refund** each owner: + /// - TAO = Σ(position.tao + position.fee_tao) → credited to the owner's **coldkey** free balance. + /// - ALPHA = Σ(position.alpha + position.fee_alpha) → credited back across **all owned hotkeys** + /// using `refund_alpha_to_hotkeys` (pro‑rata by current α stake; even split if all zero; never skipped). + /// * Decrease "provided reserves" (principal only) for non‑protocol owners. /// * Clear ActiveTickIndexManager entries, ticks, fee globals, price, tick, liquidity, /// init flag, bitmap words, fee rate knob, and user LP flag. /// /// - **V2 / non‑V3 path**: - /// * No per‑position records exist; still defensively clear the same V3 storages - /// (safe no‑ops) so the subnet leaves **no swap residue**. + /// * No per‑position records exist; still defensively clear the same V3 storages (safe no‑ops). pub fn do_liquidate_all_liquidity_providers(netuid: NetUid) -> DispatchResult { let mechid = T::SubnetInfo::mechanism(netuid.into()); let v3_initialized = SwapV3Initialized::::get(netuid); let user_lp_enabled = - >::is_user_liquidity_enabled(netuid); + >::is_user_liquidity_enabled(netuid); let is_v3_mode = mechid == 1 && v3_initialized; if is_v3_mode { - // -------- V3: close every position, REFUND owners, then clear all V3 state -------- + // -------- V3: close every position, aggregate refunds, clear state -------- // 1) Snapshot all (owner, position_id) under this netuid to avoid iterator aliasing. - let mut to_close: sp_std::vec::Vec<(T::AccountId, PositionId)> = - sp_std::vec::Vec::new(); + struct CloseItem { + owner: A, + pos_id: PositionId, + } + let mut to_close: sp_std::vec::Vec> = sp_std::vec::Vec::new(); for ((n, owner, pos_id), _pos) in Positions::::iter() { if n == netuid { - to_close.push((owner, pos_id)); + to_close.push(CloseItem { owner, pos_id }); } } let protocol_account = Self::protocol_account_id(); - // 2) Remove all positions (user + protocol) and REFUND both legs to the owner. - for (owner, pos_id) in to_close.into_iter() { + // 2) Aggregate refunds per owner while removing positions. + use sp_std::collections::btree_map::BTreeMap; + let mut refunds: BTreeMap = BTreeMap::new(); + + for CloseItem { owner, pos_id } in to_close.into_iter() { + // Remove position first; this returns principal + accrued fees amounts. let rm = Self::do_remove_liquidity(netuid, &owner, pos_id)?; - // Refund TAO: principal + accrued TAO fees. - let tao_refund = rm.tao.saturating_add(rm.fee_tao); - if tao_refund > TaoCurrency::ZERO { - T::BalanceOps::increase_balance(&owner, tao_refund); + // Accumulate (TAO, α) refund: principal + fees. + let tao_add = rm.tao.saturating_add(rm.fee_tao); + let alpha_add = rm.alpha.saturating_add(rm.fee_alpha); + + refunds + .entry(owner.clone()) + .and_modify(|(t, a)| { + *t = t.saturating_add(tao_add); + *a = a.saturating_add(alpha_add); + }) + .or_insert((tao_add, alpha_add)); + + // Mirror "user-provided" reserves by principal only (fees have been paid out). + if owner != protocol_account { + T::BalanceOps::decrease_provided_tao_reserve(netuid, rm.tao); + T::BalanceOps::decrease_provided_alpha_reserve(netuid, rm.alpha); } + } - // Refund ALPHA: principal + accrued ALPHA fees. - let alpha_refund = rm.alpha.saturating_add(rm.fee_alpha); - if !alpha_refund.is_zero() { - // Credit ALPHA back to the provider on (coldkey=owner, hotkey=owner). - T::BalanceOps::increase_stake(&owner, &owner, netuid.into(), alpha_refund)?; + // 3) Process refunds per owner (no skipping). + for (owner, (tao_sum, alpha_sum)) in refunds.into_iter() { + // TAO → coldkey free balance + if tao_sum > TaoCurrency::ZERO { + T::BalanceOps::increase_balance(&owner, tao_sum); } - // Mirror `remove_liquidity`: update **user-provided** reserves by principal only. - // Skip for protocol-owned liquidity which never contributed to provided reserves. - if owner != protocol_account { - T::BalanceOps::decrease_provided_tao_reserve(netuid.into(), rm.tao); - T::BalanceOps::decrease_provided_alpha_reserve(netuid.into(), rm.alpha); + // α → split across all owned hotkeys (never skip). Skip α for protocol account + // because protocol liquidity does not map to user stake. + if !alpha_sum.is_zero() && owner != protocol_account { + Self::refund_alpha_to_hotkeys(netuid, &owner, alpha_sum); } } - // 3) Clear active tick index set by walking ticks we are about to clear. + // 4) Clear active tick index set by walking ticks we are about to clear. let active_ticks: sp_std::vec::Vec = Ticks::::iter_prefix(netuid).map(|(ti, _)| ti).collect(); for ti in active_ticks { ActiveTickIndexManager::::remove(netuid, ti); } - // 4) Clear storage: + // 5) Clear storage: // Positions (StorageNMap) – prefix is **(netuid,)** not just netuid. let _ = Positions::::clear_prefix((netuid,), u32::MAX, None); From c4668d4ce05ed8f10fa1c98809747fb0e0259d0d Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:24:03 -0700 Subject: [PATCH 49/97] add test massive_dissolve --- pallets/subtensor/src/tests/networks.rs | 469 ++++++++++++++++++++++++ 1 file changed, 469 insertions(+) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index baa3910fa6..b378cf8899 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -4,6 +4,7 @@ use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; +use sp_std::collections::btree_map::BTreeMap; use substrate_fixed::types::{U64F64, U96F32}; use subtensor_runtime_common::TaoCurrency; use subtensor_swap_interface::SwapHandler; @@ -1233,3 +1234,471 @@ fn test_tempo_greater_than_weight_set_rate_limit() { assert!(tempo as u64 >= weights_set_rate_limit); }) } + +#[test] +fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state() { + new_test_ext(0).execute_with(|| { + // ──────────────────────────────────────────────────────────────────── + // 0) Constants and helpers (distinct hotkeys & coldkeys) + // ──────────────────────────────────────────────────────────────────── + const NUM_NETS: usize = 4; + + // Six LP coldkeys + let cold_lps: [U256; 6] = [ + U256::from(3001), + U256::from(3002), + U256::from(3003), + U256::from(3004), + U256::from(3005), + U256::from(3006), + ]; + + // For each coldkey, define two DISTINCT hotkeys it owns. + let mut cold_to_hots: BTreeMap = BTreeMap::new(); + for &c in cold_lps.iter() { + let h1 = U256::from(c.low_u64().saturating_add(100_000)); + let h2 = U256::from(c.low_u64().saturating_add(200_000)); + cold_to_hots.insert(c, [h1, h2]); + } + + // Distinct τ pot sizes per net. + let pots: [u64; NUM_NETS] = [12_345, 23_456, 34_567, 45_678]; + + let lp_sets_per_net: [&[U256]; NUM_NETS] = [ + &cold_lps[0..4], // net0: A,B,C,D + &cold_lps[2..6], // net1: C,D,E,F + &cold_lps[0..6], // net2: A..F + &cold_lps[1..5], // net3: B,C,D,E + ]; + + // Multiple bands/sizes → many positions per cold across nets, using mixed hotkeys. + let bands: [i32; 3] = [5, 13, 30]; + let liqs: [u64; 3] = [400_000, 700_000, 1_100_000]; + + // Helper: add a V3 position via a (hot, cold) pair. + let add_pos = |net: NetUid, hot: U256, cold: U256, band: i32, liq: u64| { + let ct = pallet_subtensor_swap::CurrentTick::::get(net); + let lo = ct.saturating_sub(band); + let hi = ct.saturating_add(band); + assert_ok!(pallet_subtensor_swap::Pallet::::add_liquidity( + RuntimeOrigin::signed(cold), + hot, + net, + lo, + hi, + liq + )); + }; + + // ──────────────────────────────────────────────────────────────────── + // 1) Create many subnets, enable V3, fix price at tick=0 (sqrt≈1) + // ──────────────────────────────────────────────────────────────────── + let mut nets: Vec = Vec::new(); + for i in 0..NUM_NETS { + let owner_hot = U256::from(10_000 + (i as u64)); + let owner_cold = U256::from(20_000 + (i as u64)); + let net = add_dynamic_network(&owner_hot, &owner_cold); + SubtensorModule::set_max_registrations_per_block(net, 1_000u16); + SubtensorModule::set_target_registrations_per_interval(net, 1_000u16); + Emission::::insert(net, Vec::::new()); + SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(0)); + + assert_ok!( + pallet_subtensor_swap::Pallet::::toggle_user_liquidity( + RuntimeOrigin::root(), + net, + true + ) + ); + + // Price/tick pinned so LP math stays stable (sqrt(1)). + let ct0 = pallet_subtensor_swap::tick::TickIndex::new_unchecked(0); + let sqrt1 = ct0.try_to_sqrt_price().expect("sqrt(1) price"); + pallet_subtensor_swap::CurrentTick::::set(net, ct0); + pallet_subtensor_swap::AlphaSqrtPrice::::set(net, sqrt1); + + nets.push(net); + } + + // Map net → index for quick lookups. + let mut net_index: BTreeMap = BTreeMap::new(); + for (i, &n) in nets.iter().enumerate() { + net_index.insert(n, i); + } + + // ──────────────────────────────────────────────────────────────────── + // 2) Pre-create a handful of small (hot, cold) pairs so accounts exist + // ──────────────────────────────────────────────────────────────────── + for id in 0u64..10 { + let cold_acc = U256::from(1_000_000 + id); + let hot_acc = U256::from(2_000_000 + id); + for &net in nets.iter() { + register_ok_neuron(net, hot_acc, cold_acc, 100_000 + id); + } + } + + // ──────────────────────────────────────────────────────────────────── + // 3) LPs per net: register each (hot, cold), massive τ prefund, and stake + // ──────────────────────────────────────────────────────────────────── + for &cold in cold_lps.iter() { + SubtensorModule::add_balance_to_coldkey_account(&cold, u64::MAX); + } + + // τ balances before LP adds (after staking): + let mut tao_before: BTreeMap = BTreeMap::new(); + + // Ordered α snapshot per net at **pair granularity** (pre‑LP): + let mut alpha_pairs_per_net: BTreeMap> = BTreeMap::new(); + + // Register both hotkeys for each participating cold on each net and stake τ→α. + for (ni, &net) in nets.iter().enumerate() { + let participants = lp_sets_per_net[ni]; + for &cold in participants.iter() { + let [hot1, hot2] = cold_to_hots[&cold]; + + // Ensure (hot, cold) neurons exist on this net. + register_ok_neuron( + net, + hot1, + cold, + (ni as u64) * 10_000 + (hot1.low_u64() % 10_000), + ); + register_ok_neuron( + net, + hot2, + cold, + (ni as u64) * 10_000 + (hot2.low_u64() % 10_000) + 1, + ); + + // Stake τ (split across the two hotkeys). + let base: u64 = + 5_000_000 + ((ni as u64) * 1_000_000) + ((cold.low_u64() % 10) * 250_000); + let stake1: u64 = base.saturating_mul(3) / 5; // 60% + let stake2: u64 = base.saturating_sub(stake1); // 40% + + assert_ok!(SubtensorModule::do_add_stake( + RuntimeOrigin::signed(cold), + hot1, + net, + stake1.into() + )); + assert_ok!(SubtensorModule::do_add_stake( + RuntimeOrigin::signed(cold), + hot2, + net, + stake2.into() + )); + } + } + + // Record τ balances now (post‑stake, pre‑LP). + for &cold in cold_lps.iter() { + tao_before.insert(cold, SubtensorModule::get_coldkey_balance(&cold)); + } + + // Capture **pair‑level** α snapshot per net (pre‑LP). + for ((hot, cold, net), amt) in Alpha::::iter() { + if let Some(&ni) = net_index.get(&net) { + if lp_sets_per_net[ni].iter().any(|&c| c == cold) { + let a: u128 = amt.saturating_to_num(); + if a > 0 { + alpha_pairs_per_net + .entry(net) + .or_default() + .push(((hot, cold), a)); + } + } + } + } + + // ──────────────────────────────────────────────────────────────────── + // 4) Add many V3 positions per cold across nets, alternating hotkeys + // ──────────────────────────────────────────────────────────────────── + for (ni, &net) in nets.iter().enumerate() { + let participants = lp_sets_per_net[ni]; + for (pi, &cold) in participants.iter().enumerate() { + let [hot1, hot2] = cold_to_hots[&cold]; + let hots = [hot1, hot2]; + for k in 0..3 { + let band = bands[(pi + k) % bands.len()]; + let liq = liqs[(ni + k) % liqs.len()]; + let hot = hots[k % hots.len()]; + add_pos(net, hot, cold, band, liq); + } + } + } + + // Snapshot τ balances AFTER LP adds (to measure actual principal debit). + let mut tao_after_adds: BTreeMap = BTreeMap::new(); + for &cold in cold_lps.iter() { + tao_after_adds.insert(cold, SubtensorModule::get_coldkey_balance(&cold)); + } + + // ──────────────────────────────────────────────────────────────────── + // 5) Compute Hamilton-apportionment BASE shares per cold and total leftover + // from the **pair-level** pre‑LP α snapshot; also count pairs per cold. + // ──────────────────────────────────────────────────────────────────── + let mut base_share_cold: BTreeMap = + cold_lps.iter().copied().map(|c| (c, 0_u64)).collect(); + let mut pair_count_cold: BTreeMap = + cold_lps.iter().copied().map(|c| (c, 0_u32)).collect(); + + let mut leftover_total: u64 = 0; + + for (ni, &net) in nets.iter().enumerate() { + let pot = pots[ni]; + let pairs = alpha_pairs_per_net.get(&net).cloned().unwrap_or_default(); + if pot == 0 || pairs.is_empty() { + continue; + } + let total_alpha: u128 = pairs.iter().map(|(_, a)| *a).sum(); + if total_alpha == 0 { + continue; + } + + let mut base_sum_net: u64 = 0; + for ((_, cold), a) in pairs.iter().copied() { + // quota = a * pot / total_alpha + let prod: u128 = (a as u128).saturating_mul(pot as u128); + let base: u64 = (prod / total_alpha) as u64; + base_sum_net = base_sum_net.saturating_add(base); + *base_share_cold.entry(cold).or_default() = + base_share_cold[&cold].saturating_add(base); + *pair_count_cold.entry(cold).or_default() += 1; + } + let leftover_net = pot.saturating_sub(base_sum_net); + leftover_total = leftover_total.saturating_add(leftover_net); + } + + // ──────────────────────────────────────────────────────────────────── + // 6) Seed τ pots and dissolve *all* networks (liquidates LPs + refunds) + // ──────────────────────────────────────────────────────────────────── + for (ni, &net) in nets.iter().enumerate() { + SubnetTAO::::insert(net, TaoCurrency::from(pots[ni])); + } + for &net in nets.iter() { + assert_ok!(SubtensorModule::do_dissolve_network(net)); + } + + // ──────────────────────────────────────────────────────────────────── + // 7) Assertions: τ balances, α gone, nets removed, swap state clean + // (Hamilton invariants enforced at cold-level without relying on tie-break) + // ──────────────────────────────────────────────────────────────────── + // Collect actual pot credits per cold (principal cancels out against adds when comparing before→after). + let mut actual_pot_cold: BTreeMap = + cold_lps.iter().copied().map(|c| (c, 0_u64)).collect(); + for &cold in cold_lps.iter() { + let before = tao_before[&cold]; + let after = SubtensorModule::get_coldkey_balance(&cold); + actual_pot_cold.insert(cold, after.saturating_sub(before)); + } + + // (a) Sum of actual pot credits equals total pots. + let total_actual: u64 = actual_pot_cold.values().copied().sum(); + let total_pots: u64 = pots.iter().copied().sum(); + assert_eq!( + total_actual, total_pots, + "total τ pot credited across colds must equal sum of pots" + ); + + // (b) Each cold’s pot is within Hamilton bounds: base ≤ actual ≤ base + #pairs. + let mut extra_accum: u64 = 0; + for &cold in cold_lps.iter() { + let base = *base_share_cold.get(&cold).unwrap_or(&0); + let pairs = *pair_count_cold.get(&cold).unwrap_or(&0) as u64; + let actual = *actual_pot_cold.get(&cold).unwrap_or(&0); + + assert!( + actual >= base, + "cold {:?} actual pot {} is below base {}", + cold, + actual, + base + ); + assert!( + actual <= base.saturating_add(pairs), + "cold {:?} actual pot {} exceeds base + pairs ({} + {})", + cold, + actual, + base, + pairs + ); + + extra_accum = extra_accum.saturating_add(actual.saturating_sub(base)); + } + + // (c) The total “extra beyond base” equals the computed leftover_total across nets. + assert_eq!( + extra_accum, leftover_total, + "sum of extras beyond base must equal total leftover" + ); + + // (d) τ principal was fully refunded (compare after_adds → after). + for &cold in cold_lps.iter() { + let before = tao_before[&cold]; + let mid = tao_after_adds[&cold]; + let after = SubtensorModule::get_coldkey_balance(&cold); + let principal_actual = before.saturating_sub(mid); + let actual_pot = after.saturating_sub(before); + assert_eq!( + after.saturating_sub(mid), + principal_actual.saturating_add(actual_pot), + "cold {:?} τ balance incorrect vs 'after_adds'", + cold + ); + } + + // For each dissolved net, check α ledgers gone, network removed, and swap state clean. + for &net in nets.iter() { + assert!( + Alpha::::iter().all(|((_h, _c, n), _)| n != net), + "alpha ledger not fully cleared for net {:?}", + net + ); + assert!( + !SubtensorModule::if_subnet_exist(net), + "subnet {:?} still exists", + net + ); + assert!( + pallet_subtensor_swap::Ticks::::iter_prefix(net) + .next() + .is_none(), + "ticks not cleared for net {:?}", + net + ); + assert!( + !pallet_subtensor_swap::Positions::::iter() + .any(|((n, _owner, _pid), _)| n == net), + "swap positions not fully cleared for net {:?}", + net + ); + assert_eq!( + pallet_subtensor_swap::FeeGlobalTao::::get(net).saturating_to_num::(), + 0, + "FeeGlobalTao nonzero for net {:?}", + net + ); + assert_eq!( + pallet_subtensor_swap::FeeGlobalAlpha::::get(net).saturating_to_num::(), + 0, + "FeeGlobalAlpha nonzero for net {:?}", + net + ); + assert_eq!( + pallet_subtensor_swap::CurrentLiquidity::::get(net), + 0, + "CurrentLiquidity not zero for net {:?}", + net + ); + assert!( + !pallet_subtensor_swap::SwapV3Initialized::::get(net), + "SwapV3Initialized still set" + ); + assert!( + !pallet_subtensor_swap::EnabledUserLiquidity::::get(net), + "EnabledUserLiquidity still set" + ); + assert!( + pallet_subtensor_swap::TickIndexBitmapWords::::iter_prefix((net,)) + .next() + .is_none(), + "TickIndexBitmapWords not cleared for net {:?}", + net + ); + } + + // ──────────────────────────────────────────────────────────────────── + // 8) Re-register a fresh subnet and re‑stake using the pallet’s min rule + // Assert αΔ equals the sim-swap result for the exact τ staked. + // ──────────────────────────────────────────────────────────────────── + let new_owner_hot = U256::from(99_000); + let new_owner_cold = U256::from(99_001); + let net_new = add_dynamic_network(&new_owner_hot, &new_owner_cold); + SubtensorModule::set_max_registrations_per_block(net_new, 1_000u16); + SubtensorModule::set_target_registrations_per_interval(net_new, 1_000u16); + Emission::::insert(net_new, Vec::::new()); + SubtensorModule::set_subnet_locked_balance(net_new, TaoCurrency::from(0)); + + assert_ok!( + pallet_subtensor_swap::Pallet::::toggle_user_liquidity( + RuntimeOrigin::root(), + net_new, + true + ) + ); + let ct0 = pallet_subtensor_swap::tick::TickIndex::new_unchecked(0); + let sqrt1 = ct0.try_to_sqrt_price().expect("sqrt(1)"); + pallet_subtensor_swap::CurrentTick::::set(net_new, ct0); + pallet_subtensor_swap::AlphaSqrtPrice::::set(net_new, sqrt1); + + // Compute the exact min stake per the pallet rule: DefaultMinStake + fee(DefaultMinStake). + let min_stake_u64: u64 = DefaultMinStake::::get().into(); + let fee_for_min: u64 = pallet_subtensor_swap::Pallet::::sim_swap( + net_new, + subtensor_swap_interface::OrderType::Buy, + min_stake_u64, + ) + .map(|r| r.fee_paid) + .unwrap_or_else(|_e| { + as subtensor_swap_interface::SwapHandler< + ::AccountId, + >>::approx_fee_amount(net_new, min_stake_u64) + }); + let min_amount_required: u64 = min_stake_u64.saturating_add(fee_for_min); + + // Re‑stake from three coldkeys; choose a specific DISTINCT hotkey per cold. + for &cold in &cold_lps[0..3] { + let [hot1, _hot2] = cold_to_hots[&cold]; + register_ok_neuron(net_new, hot1, cold, 7777); + + let before_tau = SubtensorModule::get_coldkey_balance(&cold); + let a_prev: u64 = Alpha::::get((hot1, cold, net_new)).saturating_to_num(); + + // Expected α for this exact τ, using the same sim path as the pallet. + let expected_alpha_out: u64 = pallet_subtensor_swap::Pallet::::sim_swap( + net_new, + subtensor_swap_interface::OrderType::Buy, + min_amount_required, + ) + .map(|r| r.amount_paid_out) + .expect("sim_swap must succeed for fresh net and min amount"); + + assert_ok!(SubtensorModule::do_add_stake( + RuntimeOrigin::signed(cold), + hot1, + net_new, + min_amount_required.into() + )); + + let after_tau = SubtensorModule::get_coldkey_balance(&cold); + let a_new: u64 = Alpha::::get((hot1, cold, net_new)).saturating_to_num(); + let a_delta = a_new.saturating_sub(a_prev); + + // τ decreased by exactly the amount we sent. + assert_eq!( + after_tau, + before_tau.saturating_sub(min_amount_required), + "τ did not decrease by the min required restake amount for cold {:?}", + cold + ); + + // α minted equals the simulated swap’s net out for that same τ. + assert_eq!( + a_delta, expected_alpha_out, + "α minted mismatch for cold {:?} (hot {:?}) on new net (αΔ {}, expected {})", + cold, hot1, a_delta, expected_alpha_out + ); + } + + // Ensure V3 still functional on new net: add a small position for the first cold using its hot1 + let who_cold = cold_lps[0]; + let [who_hot, _] = cold_to_hots[&who_cold]; + add_pos(net_new, who_hot, who_cold, 8, 123_456); + assert!( + pallet_subtensor_swap::Positions::::iter() + .any(|((n, owner, _pid), _)| n == net_new && owner == who_cold), + "new position not recorded on the re-registered net" + ); + }); +} From 00a7f50e668a7eb1cb37776d6258404da609c0c1 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:25:44 -0700 Subject: [PATCH 50/97] decrease subnet limit to 128 --- pallets/subtensor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 173ccd3aff..5ae36117b5 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -872,7 +872,7 @@ pub mod pallet { #[pallet::type_value] /// Default value for subnet limit. pub fn DefaultSubnetLimit() -> u16 { - 256 + 128 } #[pallet::storage] From 43bc7885af1196e129c2e34498ab3235dfb0f2dc Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:58:54 -0700 Subject: [PATCH 51/97] dissolve_all_liquidity_providers --- pallets/subtensor/src/coinbase/root.rs | 2 +- pallets/swap-interface/src/lib.rs | 2 +- pallets/swap/src/pallet/impls.rs | 29 +++++++++----------------- pallets/swap/src/pallet/tests.rs | 16 +++++++------- 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 477a42f3b4..1d4aa91191 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -374,7 +374,7 @@ impl Pallet { ); // 2. --- Perform the cleanup before removing the network. - T::SwapInterface::liquidate_all_liquidity_providers(netuid)?; + T::SwapInterface::dissolve_all_liquidity_providers(netuid)?; Self::destroy_alpha_in_out_stakes(netuid)?; // 3. --- Remove the network diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index f29357c741..268893f6a1 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -34,7 +34,7 @@ pub trait SwapHandler { alpha_delta: AlphaCurrency, ); fn is_user_liquidity_enabled(netuid: NetUid) -> bool; - fn liquidate_all_liquidity_providers(netuid: NetUid) -> DispatchResult; + fn dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult; } #[derive(Debug, PartialEq)] diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index c40579b345..3b07aaa768 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1218,11 +1218,7 @@ impl Pallet { /// If all stakes are zero, split evenly. /// - If no hotkeys exist, fall back to (coldkey, coldkey). /// - pub fn refund_alpha_to_hotkeys( - netuid: NetUid, - coldkey: &T::AccountId, - alpha_total: AlphaCurrency, - ) { + pub fn refund_alpha(netuid: NetUid, coldkey: &T::AccountId, alpha_total: AlphaCurrency) { if alpha_total.is_zero() { return; } @@ -1339,21 +1335,20 @@ impl Pallet { } } - /// Liquidate (force-close) all LPs for `netuid`, refund providers, and reset all swap state. + /// Dissolve all LPs for `netuid`, refund providers, and reset all swap state. /// /// - **V3 path** (mechanism == 1 && SwapV3Initialized): - /// * Remove **all** positions (user + protocol) via `do_remove_liquidity`. + /// * Remove **all** positions via `do_remove_liquidity`. /// * **Refund** each owner: /// - TAO = Σ(position.tao + position.fee_tao) → credited to the owner's **coldkey** free balance. - /// - ALPHA = Σ(position.alpha + position.fee_alpha) → credited back across **all owned hotkeys** - /// using `refund_alpha_to_hotkeys` (pro‑rata by current α stake; even split if all zero; never skipped). + /// - ALPHA = Σ(position.alpha + position.fee_alpha) → credited back /// * Decrease "provided reserves" (principal only) for non‑protocol owners. /// * Clear ActiveTickIndexManager entries, ticks, fee globals, price, tick, liquidity, /// init flag, bitmap words, fee rate knob, and user LP flag. /// /// - **V2 / non‑V3 path**: /// * No per‑position records exist; still defensively clear the same V3 storages (safe no‑ops). - pub fn do_liquidate_all_liquidity_providers(netuid: NetUid) -> DispatchResult { + pub fn do_dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { let mechid = T::SubnetInfo::mechanism(netuid.into()); let v3_initialized = SwapV3Initialized::::get(netuid); let user_lp_enabled = @@ -1383,7 +1378,6 @@ impl Pallet { let mut refunds: BTreeMap = BTreeMap::new(); for CloseItem { owner, pos_id } in to_close.into_iter() { - // Remove position first; this returns principal + accrued fees amounts. let rm = Self::do_remove_liquidity(netuid, &owner, pos_id)?; // Accumulate (TAO, α) refund: principal + fees. @@ -1398,7 +1392,6 @@ impl Pallet { }) .or_insert((tao_add, alpha_add)); - // Mirror "user-provided" reserves by principal only (fees have been paid out). if owner != protocol_account { T::BalanceOps::decrease_provided_tao_reserve(netuid, rm.tao); T::BalanceOps::decrease_provided_alpha_reserve(netuid, rm.alpha); @@ -1415,7 +1408,7 @@ impl Pallet { // α → split across all owned hotkeys (never skip). Skip α for protocol account // because protocol liquidity does not map to user stake. if !alpha_sum.is_zero() && owner != protocol_account { - Self::refund_alpha_to_hotkeys(netuid, &owner, alpha_sum); + Self::refund_alpha(netuid, &owner, alpha_sum); } } @@ -1443,13 +1436,11 @@ impl Pallet { // Active tick bitmap words (StorageNMap) – prefix is **(netuid,)**. let _ = TickIndexBitmapWords::::clear_prefix((netuid,), u32::MAX, None); - - // Remove knobs (safe on deregistration). FeeRate::::remove(netuid); EnabledUserLiquidity::::remove(netuid); log::debug!( - "liquidate_all_liquidity_providers: netuid={:?}, mode=V3, user_lp_enabled={}, v3_state_cleared + refunds", + "dissolve_all_liquidity_providers: netuid={:?}, mode=V3, user_lp_enabled={}, v3_state_cleared + refunds", netuid, user_lp_enabled ); @@ -1484,7 +1475,7 @@ impl Pallet { EnabledUserLiquidity::::remove(netuid); log::debug!( - "liquidate_all_liquidity_providers: netuid={:?}, mode=V2-or-nonV3, user_lp_enabled={}, state_cleared", + "dissolve_all_liquidity_providers: netuid={:?}, mode=V2-or-nonV3, user_lp_enabled={}, state_cleared", netuid, user_lp_enabled ); @@ -1576,8 +1567,8 @@ impl SwapHandler for Pallet { fn is_user_liquidity_enabled(netuid: NetUid) -> bool { EnabledUserLiquidity::::get(netuid) } - fn liquidate_all_liquidity_providers(netuid: NetUid) -> DispatchResult { - Self::do_liquidate_all_liquidity_providers(netuid) + fn dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { + Self::do_dissolve_all_liquidity_providers(netuid) } } diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index 6b5204f536..e6148ed94e 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -1977,7 +1977,7 @@ fn test_liquidate_v3_removes_positions_ticks_and_state() { assert!(had_bitmap_words); // ACT: Liquidate & reset swap state - assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); // ASSERT: positions cleared (both user and protocol) assert_eq!( @@ -2062,7 +2062,7 @@ fn test_liquidate_v3_with_user_liquidity_disabled() { )); // ACT - assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); // ASSERT: positions & ticks gone, state reset assert_eq!( @@ -2107,7 +2107,7 @@ fn test_liquidate_non_v3_uninitialized_ok_and_clears() { ); // ACT - assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); // ASSERT: Defensive clears leave no residues and do not panic assert!( @@ -2160,9 +2160,9 @@ fn test_liquidate_idempotent() { )); // 1st liquidation - assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); // 2nd liquidation (no state left) — must still succeed - assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); // State remains empty assert!( @@ -2184,8 +2184,8 @@ fn test_liquidate_idempotent() { let netuid = NetUid::from(8); // Never initialize V3 - assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); - assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); assert!( Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) @@ -2252,7 +2252,7 @@ fn liquidate_v3_refunds_user_funds_and_clears_state() { ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), alpha_taken); // Liquidate everything on the subnet. - assert_ok!(Pallet::::do_liquidate_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); // Expect balances restored to BEFORE snapshots (no swaps ran -> zero fees). // TAO: we withdrew 'need_tao' above and liquidation refunded it, so we should be back to 'tao_before'. From f22ef77bb5398ba5500fbc362007d862619fb13e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:39:35 -0700 Subject: [PATCH 52/97] clippy --- pallets/admin-utils/src/lib.rs | 2 +- pallets/subtensor/src/coinbase/root.rs | 4 +- pallets/subtensor/src/tests/networks.rs | 65 +++++++------------- pallets/swap/src/pallet/impls.rs | 82 ++++++++++++------------- 4 files changed, 63 insertions(+), 90 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 22e1c4e6e3..9b60377b03 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -972,7 +972,7 @@ pub mod pallet { pub fn sudo_set_subnet_limit(origin: OriginFor, max_subnets: u16) -> DispatchResult { ensure_root(origin)?; pallet_subtensor::Pallet::::set_max_subnets(max_subnets); - log::debug!("MaxSubnets ( max_subnets: {:?} ) ", max_subnets); + log::debug!("MaxSubnets ( max_subnets: {max_subnets:?} ) "); Ok(()) } diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 1d4aa91191..8a95c9e8df 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -477,9 +477,7 @@ impl Pallet { // --- Log final removal. log::debug!( - "remove_network: netuid={}, owner={:?} removed successfully", - netuid, - owner_coldkey + "remove_network: netuid={netuid}, owner={owner_coldkey:?} removed successfully" ); } diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index b378cf8899..4659ee4bbe 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -117,6 +117,7 @@ fn dissolve_single_alpha_out_staker_gets_all_tao() { }); } +#[allow(clippy::indexing_slicing)] #[test] fn dissolve_two_stakers_pro_rata_distribution() { new_test_ext(0).execute_with(|| { @@ -142,8 +143,8 @@ fn dissolve_two_stakers_pro_rata_distribution() { // Expected τ shares with largest remainder let total = a1 + a2; - let prod1 = (a1 as u128) * (pot as u128); - let prod2 = (a2 as u128) * (pot as u128); + let prod1 = a1 * (pot as u128); + let prod2 = a2 * (pot as u128); let share1 = (prod1 / total) as u64; let share2 = (prod2 / total) as u64; let mut distributed = share1 + share2; @@ -151,12 +152,8 @@ fn dissolve_two_stakers_pro_rata_distribution() { if distributed < pot { rem.sort_by_key(|&(_c, r)| core::cmp::Reverse(r)); let leftover = pot - distributed; - for i in 0..(leftover as usize) { - if rem[i].0 == s1_cold { - distributed += 1; - } else { - distributed += 1; - } + for _ in 0..leftover as usize { + distributed += 1; } } // Recompute exact expected shares using the same logic @@ -694,8 +691,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { assert_eq!( SubtensorModule::get_coldkey_balance(&cold[i]), bal_before[i] + share[i], - "staker {} cold-key balance changed unexpectedly", - i + "staker {i} cold-key balance changed unexpectedly" ); } @@ -1235,6 +1231,7 @@ fn test_tempo_greater_than_weight_set_rate_limit() { }) } +#[allow(clippy::indexing_slicing)] #[test] fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state() { new_test_ext(0).execute_with(|| { @@ -1399,7 +1396,7 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( // Capture **pair‑level** α snapshot per net (pre‑LP). for ((hot, cold, net), amt) in Alpha::::iter() { if let Some(&ni) = net_index.get(&net) { - if lp_sets_per_net[ni].iter().any(|&c| c == cold) { + if lp_sets_per_net[ni].contains(&cold) { let a: u128 = amt.saturating_to_num(); if a > 0 { alpha_pairs_per_net @@ -1459,7 +1456,7 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( let mut base_sum_net: u64 = 0; for ((_, cold), a) in pairs.iter().copied() { // quota = a * pot / total_alpha - let prod: u128 = (a as u128).saturating_mul(pot as u128); + let prod: u128 = a.saturating_mul(pot as u128); let base: u64 = (prod / total_alpha) as u64; base_sum_net = base_sum_net.saturating_add(base); *base_share_cold.entry(cold).or_default() = @@ -1510,18 +1507,11 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( assert!( actual >= base, - "cold {:?} actual pot {} is below base {}", - cold, - actual, - base + "cold {cold:?} actual pot {actual} is below base {base}" ); assert!( actual <= base.saturating_add(pairs), - "cold {:?} actual pot {} exceeds base + pairs ({} + {})", - cold, - actual, - base, - pairs + "cold {cold:?} actual pot {actual} exceeds base + pairs ({base} + {pairs})" ); extra_accum = extra_accum.saturating_add(actual.saturating_sub(base)); @@ -1543,8 +1533,7 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( assert_eq!( after.saturating_sub(mid), principal_actual.saturating_add(actual_pot), - "cold {:?} τ balance incorrect vs 'after_adds'", - cold + "cold {cold:?} τ balance incorrect vs 'after_adds'" ); } @@ -1552,44 +1541,37 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( for &net in nets.iter() { assert!( Alpha::::iter().all(|((_h, _c, n), _)| n != net), - "alpha ledger not fully cleared for net {:?}", - net + "alpha ledger not fully cleared for net {net:?}" ); assert!( !SubtensorModule::if_subnet_exist(net), - "subnet {:?} still exists", - net + "subnet {net:?} still exists" ); assert!( pallet_subtensor_swap::Ticks::::iter_prefix(net) .next() .is_none(), - "ticks not cleared for net {:?}", - net + "ticks not cleared for net {net:?}" ); assert!( !pallet_subtensor_swap::Positions::::iter() .any(|((n, _owner, _pid), _)| n == net), - "swap positions not fully cleared for net {:?}", - net + "swap positions not fully cleared for net {net:?}" ); assert_eq!( pallet_subtensor_swap::FeeGlobalTao::::get(net).saturating_to_num::(), 0, - "FeeGlobalTao nonzero for net {:?}", - net + "FeeGlobalTao nonzero for net {net:?}" ); assert_eq!( pallet_subtensor_swap::FeeGlobalAlpha::::get(net).saturating_to_num::(), 0, - "FeeGlobalAlpha nonzero for net {:?}", - net + "FeeGlobalAlpha nonzero for net {net:?}" ); assert_eq!( pallet_subtensor_swap::CurrentLiquidity::::get(net), 0, - "CurrentLiquidity not zero for net {:?}", - net + "CurrentLiquidity not zero for net {net:?}" ); assert!( !pallet_subtensor_swap::SwapV3Initialized::::get(net), @@ -1603,8 +1585,7 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( pallet_subtensor_swap::TickIndexBitmapWords::::iter_prefix((net,)) .next() .is_none(), - "TickIndexBitmapWords not cleared for net {:?}", - net + "TickIndexBitmapWords not cleared for net {net:?}" ); } @@ -1679,15 +1660,13 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( assert_eq!( after_tau, before_tau.saturating_sub(min_amount_required), - "τ did not decrease by the min required restake amount for cold {:?}", - cold + "τ did not decrease by the min required restake amount for cold {cold:?}" ); // α minted equals the simulated swap’s net out for that same τ. assert_eq!( a_delta, expected_alpha_out, - "α minted mismatch for cold {:?} (hot {:?}) on new net (αΔ {}, expected {})", - cold, hot1, a_delta, expected_alpha_out + "α minted mismatch for cold {cold:?} (hot {hot1:?}) on new net (αΔ {a_delta}, expected {expected_alpha_out})" ); } diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 3b07aaa768..9befe6b828 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1217,7 +1217,6 @@ impl Pallet { /// - If the coldkey owns multiple hotkeys, split pro‑rata by current α stake on this subnet. /// If all stakes are zero, split evenly. /// - If no hotkeys exist, fall back to (coldkey, coldkey). - /// pub fn refund_alpha(netuid: NetUid, coldkey: &T::AccountId, alpha_total: AlphaCurrency) { if alpha_total.is_zero() { return; @@ -1237,44 +1236,53 @@ impl Pallet { .iter() .map(|hk| { let bal = T::BalanceOps::alpha_balance(netuid, coldkey, hk); - bal.to_u64() as u128 + u128::from(bal.to_u64()) }) .collect(); - let sum_weights: u128 = weights.iter().copied().sum(); - let n: u128 = hotkeys.len() as u128; + let sum_weights: u128 = weights + .iter() + .copied() + .fold(0u128, |acc, w| acc.saturating_add(w)); + let n: u128 = u128::from(hotkeys.len() as u64); - let total_alpha_u128: u128 = alpha_total.to_u64() as u128; + let total_alpha_u128: u128 = u128::from(alpha_total.to_u64()); // 3) Compute integer shares with remainder handling. let mut shares: sp_std::vec::Vec<(T::AccountId, u64)> = sp_std::vec::Vec::with_capacity(hotkeys.len()); + if sum_weights > 0 { // Pro‑rata by weights. let mut assigned: u128 = 0; for (hk, w) in hotkeys.iter().cloned().zip(weights.iter().copied()) { - let part: u128 = (total_alpha_u128.saturating_mul(w)) / sum_weights; - shares.push((hk, part as u64)); + let numerator = total_alpha_u128.saturating_mul(w); + let part: u128 = numerator.checked_div(sum_weights).unwrap_or(0); + shares.push((hk, u64::try_from(part).unwrap_or(u64::MAX))); assigned = assigned.saturating_add(part); } - let mut remainder: u64 = total_alpha_u128 - .saturating_sub(assigned) - .try_into() - .unwrap_or(0); - let len = shares.len(); - let mut i = 0usize; - while remainder > 0 && i < len { - shares[i].1 = shares[i].1.saturating_add(1); - remainder = remainder.saturating_sub(1); - i += 1; + + // Distribute remainder one‑by‑one. + let mut remainder: u128 = total_alpha_u128.saturating_sub(assigned); + let mut i: usize = 0; + while remainder > 0 && i < shares.len() { + if let Some(pair) = shares.get_mut(i) { + pair.1 = pair.1.saturating_add(1); + remainder = remainder.saturating_sub(1); + i = i.saturating_add(1); + } else { + break; + } } } else { // Even split. - let base: u64 = (total_alpha_u128 / n) as u64; - let mut remainder: u64 = (total_alpha_u128 % n) as u64; + let base_u128 = total_alpha_u128.checked_div(n).unwrap_or(0); + let mut remainder_u128 = total_alpha_u128.checked_rem(n).unwrap_or(0); + + let base: u64 = u64::try_from(base_u128).unwrap_or(u64::MAX); for hk in hotkeys.into_iter() { - let add_one = if remainder > 0 { - remainder = remainder.saturating_sub(1); + let add_one: u64 = if remainder_u128 > 0 { + remainder_u128 = remainder_u128.saturating_sub(1); 1 } else { 0 @@ -1296,12 +1304,7 @@ impl Pallet { Ok(_) => successes.push(hk.clone()), Err(e) => { log::warn!( - "refund_alpha_to_hotkeys: increase_stake failed (cold={:?}, hot={:?}, netuid={:?}, amt={:?}): {:?}", - coldkey, - hk, - netuid, - amt_u64, - e + "refund_alpha_to_hotkeys: increase_stake failed (cold={coldkey:?}, hot={hk:?}, netuid={netuid:?}, amt={amt_u64:?}): {e:?}" ); leftover = leftover.saturating_add(*amt_u64); } @@ -1310,13 +1313,13 @@ impl Pallet { // 5) Retry: spread any leftover across the hotkeys that succeeded in step 4. if leftover > 0 && !successes.is_empty() { - let count = successes.len() as u64; - let base = leftover / count; - let mut rem = leftover % count; + let count_u64 = successes.len() as u64; + let base = leftover.checked_div(count_u64).unwrap_or(0); + let mut rem = leftover.checked_rem(count_u64).unwrap_or(0); for hk in successes.iter() { let add: u64 = base.saturating_add(if rem > 0 { - rem -= 1; + rem = rem.saturating_sub(1); 1 } else { 0 @@ -1341,7 +1344,7 @@ impl Pallet { /// * Remove **all** positions via `do_remove_liquidity`. /// * **Refund** each owner: /// - TAO = Σ(position.tao + position.fee_tao) → credited to the owner's **coldkey** free balance. - /// - ALPHA = Σ(position.alpha + position.fee_alpha) → credited back + /// - ALPHA = Σ(position.alpha + position.fee_alpha) → credited back via `refund_alpha`. /// * Decrease "provided reserves" (principal only) for non‑protocol owners. /// * Clear ActiveTickIndexManager entries, ticks, fee globals, price, tick, liquidity, /// init flag, bitmap words, fee rate knob, and user LP flag. @@ -1398,15 +1401,14 @@ impl Pallet { } } - // 3) Process refunds per owner (no skipping). + // 3) Process refunds per owner. for (owner, (tao_sum, alpha_sum)) in refunds.into_iter() { // TAO → coldkey free balance if tao_sum > TaoCurrency::ZERO { T::BalanceOps::increase_balance(&owner, tao_sum); } - // α → split across all owned hotkeys (never skip). Skip α for protocol account - // because protocol liquidity does not map to user stake. + // α → split across all hotkeys owned by `owner`. if !alpha_sum.is_zero() && owner != protocol_account { Self::refund_alpha(netuid, &owner, alpha_sum); } @@ -1440,9 +1442,7 @@ impl Pallet { EnabledUserLiquidity::::remove(netuid); log::debug!( - "dissolve_all_liquidity_providers: netuid={:?}, mode=V3, user_lp_enabled={}, v3_state_cleared + refunds", - netuid, - user_lp_enabled + "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V3, user_lp_enabled={user_lp_enabled}, v3_state_cleared + refunds" ); return Ok(()); @@ -1450,10 +1450,8 @@ impl Pallet { // -------- V2 / non‑V3: no positions to close; still nuke any V3 residues -------- - // Positions (StorageNMap) – prefix is (netuid,) let _ = Positions::::clear_prefix((netuid,), u32::MAX, None); - // Active ticks set via ticks present (if any) let active_ticks: sp_std::vec::Vec = Ticks::::iter_prefix(netuid).map(|(ti, _)| ti).collect(); for ti in active_ticks { @@ -1475,9 +1473,7 @@ impl Pallet { EnabledUserLiquidity::::remove(netuid); log::debug!( - "dissolve_all_liquidity_providers: netuid={:?}, mode=V2-or-nonV3, user_lp_enabled={}, state_cleared", - netuid, - user_lp_enabled + "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V2-or-nonV3, user_lp_enabled={user_lp_enabled}, state_cleared" ); Ok(()) From d4d8619ba81594cf689404e84daa69f6c0be698e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:40:07 -0700 Subject: [PATCH 53/97] bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8962b762f4..5bd265f189 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -213,7 +213,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: 302, + spec_version: 303, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From b75c6e1f7c7f8a2aa7f18211a547d28ab52a2ce1 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 21 Aug 2025 09:04:41 -0700 Subject: [PATCH 54/97] DefaultSubnetLimit 148 --- pallets/subtensor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 5ae36117b5..916996c5b4 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -872,7 +872,7 @@ pub mod pallet { #[pallet::type_value] /// Default value for subnet limit. pub fn DefaultSubnetLimit() -> u16 { - 128 + 148 } #[pallet::storage] From adf9d251c0addf9d3944f11f82ef6c9746ecd335 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 21 Aug 2025 11:16:03 -0700 Subject: [PATCH 55/97] use price instead of emission --- pallets/subtensor/src/coinbase/root.rs | 28 ++++++++++++++------------ pallets/subtensor/src/macros/errors.rs | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 8a95c9e8df..f5e02371ef 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -22,6 +22,7 @@ use frame_support::weights::Weight; use safe_math::*; use sp_core::Get; use substrate_fixed::types::I64F64; +use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -583,14 +584,19 @@ impl Pallet { 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 = u64::MAX; + let mut candidate_price: U96F32 = U96F32::saturating_from_num(u128::MAX); let mut candidate_timestamp: u64 = u64::MAX; - for net in 1..=total_networks { - let netuid: NetUid = net.into(); + for (netuid, added) in NetworksAdded::::iter() { + if !added || netuid == NetUid::ROOT { + continue; + } + if !Self::if_subnet_exist(netuid) { + continue; + } + let registered_at = NetworkRegisteredAt::::get(netuid); // Skip immune networks. @@ -598,18 +604,14 @@ impl Pallet { continue; } - // Sum AlphaCurrency as u64 for comparison. - let total_emission: u64 = Emission::::get(netuid) - .into_iter() - .map(Into::::into) - .sum(); + let price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); - // If tie on total_emission, earliest registration wins. - if total_emission < candidate_emission - || (total_emission == candidate_emission && registered_at < candidate_timestamp) + // If tie on price, earliest registration wins. + if price < candidate_price + || (price == candidate_price && registered_at < candidate_timestamp) { candidate_netuid = Some(netuid); - candidate_emission = total_emission; + candidate_price = price; candidate_timestamp = registered_at; } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 3708c5c9cd..a5fcce3da4 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -244,7 +244,7 @@ mod errors { SymbolAlreadyInUse, /// Incorrect commit-reveal version. IncorrectCommitRevealVersion, - /// Subnet limit reached & no eligible subnet to prune + /// Subnet limit reached & there is no eligible subnet to prune SubnetLimitReached, } } From 203ebf21dd13b591565a8a2f29458e71ae0cf6e2 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 21 Aug 2025 11:34:17 -0700 Subject: [PATCH 56/97] update prune tests for price --- pallets/subtensor/src/tests/networks.rs | 27 ++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 4659ee4bbe..7dad4f8543 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -5,7 +5,7 @@ use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; use sp_std::collections::btree_map::BTreeMap; -use substrate_fixed::types::{U64F64, U96F32}; +use substrate_fixed::types::{U64F64, U96F32, I96F32}; use subtensor_runtime_common::TaoCurrency; use subtensor_swap_interface::SwapHandler; @@ -731,7 +731,7 @@ fn prune_none_when_all_networks_immune() { } #[test] -fn prune_selects_network_with_lowest_emission() { +fn prune_selects_network_with_lowest_price() { new_test_ext(0).execute_with(|| { let n1 = add_dynamic_network(&U256::from(20), &U256::from(10)); let n2 = add_dynamic_network(&U256::from(40), &U256::from(30)); @@ -740,16 +740,16 @@ fn prune_selects_network_with_lowest_emission() { let imm = SubtensorModule::get_network_immunity_period(); System::set_block_number(imm + 10); - // n1 has lower total emission - Emission::::insert(n1, vec![AlphaCurrency::from(5)]); - Emission::::insert(n2, vec![AlphaCurrency::from(100)]); + // n1 has lower price → should be pruned + SubnetMovingPrice::::insert(n1, I96F32::from_num(1)); + SubnetMovingPrice::::insert(n2, I96F32::from_num(10)); assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); }); } #[test] -fn prune_ignores_immune_network_even_if_lower_emission() { +fn prune_ignores_immune_network_even_if_lower_price() { new_test_ext(0).execute_with(|| { // create mature network n1 first let n1 = add_dynamic_network(&U256::from(22), &U256::from(11)); @@ -760,9 +760,9 @@ fn prune_ignores_immune_network_even_if_lower_emission() { // create second network n2 *inside* immunity let n2 = add_dynamic_network(&U256::from(44), &U256::from(33)); - // emissions: n1 bigger, n2 smaller but immune - Emission::::insert(n1, vec![AlphaCurrency::from(50)]); - Emission::::insert(n2, vec![AlphaCurrency::from(1)]); + // prices: n2 lower but immune; n1 must be selected + SubnetMovingPrice::::insert(n1, I96F32::from_num(5)); + SubnetMovingPrice::::insert(n2, I96F32::from_num(1)); System::set_block_number(imm + 10); // still immune for n2 assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); @@ -770,7 +770,7 @@ fn prune_ignores_immune_network_even_if_lower_emission() { } #[test] -fn prune_tie_on_emission_earlier_registration_wins() { +fn prune_tie_on_price_earlier_registration_wins() { new_test_ext(0).execute_with(|| { // n1 registered first let n1 = add_dynamic_network(&U256::from(66), &U256::from(55)); @@ -783,11 +783,10 @@ fn prune_tie_on_emission_earlier_registration_wins() { let imm = SubtensorModule::get_network_immunity_period(); System::set_block_number(imm + 20); - // identical emissions → tie - Emission::::insert(n1, vec![AlphaCurrency::from(123)]); - Emission::::insert(n2, vec![AlphaCurrency::from(123)]); + // identical prices → tie; earlier (n1) must be chosen + SubnetMovingPrice::::insert(n1, I96F32::from_num(7)); + SubnetMovingPrice::::insert(n2, I96F32::from_num(7)); - // earlier (n1) must be chosen assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); }); } From a0c93a86c961f94ba4e23ea5d785d9c86f009a68 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:18:03 -0700 Subject: [PATCH 57/97] add test prune_selection_complex_state_exhaustive --- pallets/subtensor/src/tests/networks.rs | 213 ++++++++++++++++++++++-- 1 file changed, 196 insertions(+), 17 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 7dad4f8543..55eba699fd 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -3,9 +3,10 @@ use crate::migrations::migrate_network_immunity_period; use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; +use pallet_subtensor_swap::{AlphaSqrtPrice, SwapV3Initialized}; use sp_core::U256; use sp_std::collections::btree_map::BTreeMap; -use substrate_fixed::types::{U64F64, U96F32, I96F32}; +use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::TaoCurrency; use subtensor_swap_interface::SwapHandler; @@ -792,29 +793,207 @@ fn prune_tie_on_price_earlier_registration_wins() { } #[test] -fn register_network_under_limit_success() { +fn prune_selection_complex_state_exhaustive() { new_test_ext(0).execute_with(|| { - SubnetLimit::::put(32u16); + let imm = SubtensorModule::get_network_immunity_period(); - let total_before = TotalNetworks::::get(); + // --------------------------------------------------------------------- + // Build a rich topology of networks with controlled registration times. + // --------------------------------------------------------------------- + // n1 + n2 in the same block (equal timestamp) to test "tie + same time". + System::set_block_number(0); + let n1 = add_dynamic_network(&U256::from(101), &U256::from(201)); + let n2 = add_dynamic_network(&U256::from(102), &U256::from(202)); // same registered_at as n1 - let cold = U256::from(10); - let hot = U256::from(11); + // Later registrations (strictly greater timestamp than n1/n2) + System::set_block_number(1); + let n3 = add_dynamic_network(&U256::from(103), &U256::from(203)); - let lock_now: u64 = SubtensorModule::get_network_lock_cost().into(); - SubtensorModule::add_balance_to_coldkey_account(&cold, lock_now.saturating_mul(10)); + System::set_block_number(2); + let n4 = add_dynamic_network(&U256::from(104), &U256::from(204)); - assert_ok!(SubtensorModule::do_register_network( - RuntimeOrigin::signed(cold), - &hot, - 1, + // Create *immune* networks that will remain ineligible initially, + // even if their price is the lowest. + System::set_block_number(imm + 5); + let n5 = add_dynamic_network(&U256::from(105), &U256::from(205)); // immune at first + + System::set_block_number(imm + 6); + let n6 = add_dynamic_network(&U256::from(106), &U256::from(206)); // immune at first + + // (Root is ignored by the selector; we may still set it for completeness.) + let root = NetUid::ROOT; + + // --------------------------------------------------------------------- + // Drive price via V3 sqrt-price path: price = (sqrt_price)^2 + // Ensure V3 is initialized so current_alpha_price() uses sqrt-price. + // --------------------------------------------------------------------- + for net in [n1, n2, n3, n4, n5, n6] { + assert_ok!( + pallet_subtensor_swap::Pallet::::toggle_user_liquidity( + RuntimeOrigin::root(), + net, + true + ) + ); + SwapV3Initialized::::insert(net, true); + } + + // sqrt prices → prices: + // n1: sqrt=5 → price 25 + // n2: sqrt=5 → price 25 + // n3: sqrt=10 → price 100 + // n4: sqrt=1 → price 1 (lowest among matured initially) + // n5: sqrt=0 → price 0 (lowest overall but immune initially) + // n6: sqrt=0 → price 0 (lowest overall but immune initially) + AlphaSqrtPrice::::insert(n1, U64F64::from_num(5)); + AlphaSqrtPrice::::insert(n2, U64F64::from_num(5)); + AlphaSqrtPrice::::insert(n3, U64F64::from_num(10)); + AlphaSqrtPrice::::insert(n4, U64F64::from_num(1)); + AlphaSqrtPrice::::insert(n5, U64F64::from_num(0)); + AlphaSqrtPrice::::insert(n6, U64F64::from_num(0)); + AlphaSqrtPrice::::insert(root, U64F64::from_num(0)); + + // --------------------------------------------------------------------- + // Phase A: Only n1..n4 are mature → lowest price (n4=1) should win. + // --------------------------------------------------------------------- + System::set_block_number(imm + 10); + assert_eq!( + SubtensorModule::get_network_to_prune(), + Some(n4), + "Among mature nets (n1..n4), n4 has price=1 (lowest) and should be chosen." + ); + + // --------------------------------------------------------------------- + // Phase B: Tie on price with *same registration time* (n1 vs n2). + // Raise n4's price to 25 (sqrt=5) so {n1=25, n2=25, n3=100, n4=25}. + // n1 and n2 share the *same registered_at*. The tie should keep the + // first encountered (stable iteration by key order) → n1. + // --------------------------------------------------------------------- + AlphaSqrtPrice::::insert(n4, U64F64::from_num(5)); // price now 25 + assert_eq!( + SubtensorModule::get_network_to_prune(), + Some(n1), + "Tie on price with equal timestamps (n1,n2) → first encountered (n1) should persist." + ); + + // --------------------------------------------------------------------- + // Phase C: Tie on price with *different registration times*. + // Make n3 price=25 as well (sqrt=5). Now n1,n2,n3,n4 all have price=25. + // Earliest registration time among them is n1 (block 0). + // --------------------------------------------------------------------- + AlphaSqrtPrice::::insert(n3, U64F64::from_num(5)); // price now 25 + assert_eq!( + SubtensorModule::get_network_to_prune(), + Some(n1), + "Tie on price across multiple nets → earliest registration (n1) wins." + ); + + // --------------------------------------------------------------------- + // Phase D: Immune networks ignored even if strictly cheaper (0). + // n5 and n6 price=0 but still immune at (imm + 10). Ensure they are + // ignored and selection remains n1. + // --------------------------------------------------------------------- + let now = System::block_number(); + assert!( + now < NetworkRegisteredAt::::get(n5) + imm, + "n5 is immune at current block" + ); + assert!( + now < NetworkRegisteredAt::::get(n6) + imm, + "n6 is immune at current block" + ); + assert_eq!( + SubtensorModule::get_network_to_prune(), + Some(n1), + "Immune nets (n5,n6) must be ignored despite lower price." + ); + + // --------------------------------------------------------------------- + // Phase E: If *all* networks are immune → return None. + // Move clock back before any network's immunity expires. + // --------------------------------------------------------------------- + System::set_block_number(0); + assert_eq!( + SubtensorModule::get_network_to_prune(), None, - )); + "With all networks immune, there is no prunable candidate." + ); - assert_eq!(TotalNetworks::::get(), total_before + 1); - let new_id: NetUid = TotalNetworks::::get().into(); - assert_eq!(SubnetOwner::::get(new_id), cold); - assert_eq!(SubnetOwnerHotkey::::get(new_id), hot); + // --------------------------------------------------------------------- + // Phase F: Advance beyond immunity for n5 & n6. + // Both n5 and n6 now eligible with price=0 (lowest). + // Tie on price; earlier registration between n5 and n6 is n5. + // --------------------------------------------------------------------- + System::set_block_number(2 * imm + 10); + assert!( + System::block_number() >= NetworkRegisteredAt::::get(n5) + imm, + "n5 has matured" + ); + assert!( + System::block_number() >= NetworkRegisteredAt::::get(n6) + imm, + "n6 has matured" + ); + assert_eq!( + SubtensorModule::get_network_to_prune(), + Some(n5), + "After immunity, n5 (price=0) should win; tie with n6 broken by earlier registration." + ); + + // --------------------------------------------------------------------- + // Phase G: Create *sparse* netuids and ensure selection is stable. + // Remove n5; now n6 (price=0) should be selected. + // This validates robustness to holes / non-contiguous netuids. + // --------------------------------------------------------------------- + SubtensorModule::remove_network(n5); + assert_eq!( + SubtensorModule::get_network_to_prune(), + Some(n6), + "After removing n5, next-lowest (n6=0) should be chosen even with sparse netuids." + ); + + // --------------------------------------------------------------------- + // Phase H: Dynamic price changes. + // Make n6 expensive (sqrt=10 → price=100); make n3 cheapest (sqrt=1 → price=1). + // --------------------------------------------------------------------- + AlphaSqrtPrice::::insert(n6, U64F64::from_num(10)); // price 100 + AlphaSqrtPrice::::insert(n3, U64F64::from_num(1)); // price 1 + assert_eq!( + SubtensorModule::get_network_to_prune(), + Some(n3), + "Dynamic changes: n3 set to price=1 (lowest among eligibles) → should be pruned." + ); + + // --------------------------------------------------------------------- + // Phase I: Tie again (n2 vs n3) but earlier registration must win. + // Give n2 the same price as n3; n2 registered at block 0, n3 at block 1. + // n2 should be chosen. + // --------------------------------------------------------------------- + AlphaSqrtPrice::::insert(n2, U64F64::from_num(1)); // price 1 + assert_eq!( + SubtensorModule::get_network_to_prune(), + Some(n2), + "Tie on price across n2 (earlier reg) and n3 → n2 wins by timestamp." + ); + + // --------------------------------------------------------------------- + // (Extra) Mark n2 as 'not added' to assert we honor the `added` flag, + // then restore it to avoid side-effects on subsequent tests. + // --------------------------------------------------------------------- + NetworksAdded::::insert(n2, false); + assert_ne!( + SubtensorModule::get_network_to_prune(), + Some(n2), + "`added=false` must exclude n2 from consideration." + ); + NetworksAdded::::insert(n2, true); + + // Root is always ignored even if cheapest. + AlphaSqrtPrice::::insert(root, U64F64::from_num(0)); + assert_ne!( + SubtensorModule::get_network_to_prune(), + Some(root), + "ROOT must never be selected for pruning." + ); }); } From 8ab9fba9452b602674d2b77b69b604e110283208 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:32:13 -0700 Subject: [PATCH 58/97] add a few missing maps --- pallets/subtensor/src/coinbase/root.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index f5e02371ef..995071922b 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -466,11 +466,12 @@ impl Pallet { SubnetTaoInEmission::::remove(netuid); SubnetVolume::::remove(netuid); SubnetMovingPrice::::remove(netuid); - - // --- 12. Add the balance back to the owner. + TokenSymbol::::remove(netuid); + SubnetMechanism::::remove(netuid); + SubnetOwnerHotkey::::remove(netuid); SubnetOwner::::remove(netuid); - // --- 13. Remove subnet identity if it exists. + // --- 11. Remove subnet identity if it exists. if SubnetIdentitiesV3::::contains_key(netuid) { SubnetIdentitiesV3::::remove(netuid); Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); From 448a78d955a721fcf49ec5b1a251c08b201c0cf0 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:50:45 -0700 Subject: [PATCH 59/97] clear additional maps --- pallets/subtensor/src/coinbase/root.rs | 220 ++++++++++++++++++++++-- pallets/subtensor/src/tests/networks.rs | 208 ++++++++++++++++++++-- 2 files changed, 405 insertions(+), 23 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 995071922b..b37d655d6f 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -16,13 +16,10 @@ // DEALINGS IN THE SOFTWARE. use super::*; -use frame_support::dispatch::Pays; -use frame_support::storage::IterableStorageDoubleMap; -use frame_support::weights::Weight; +use frame_support::{dispatch::Pays, weights::Weight}; use safe_math::*; use sp_core::Get; -use substrate_fixed::types::I64F64; -use substrate_fixed::types::U96F32; +use substrate_fixed::types::{I64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -418,10 +415,11 @@ impl Pallet { let _ = Weights::::clear_prefix(netuid, u32::MAX, None); // --- 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( - NetUid::ROOT, - ) + for (uid_i, weights_i) in as frame_support::storage::IterableStorageDoubleMap< + NetUid, + u16, + sp_std::vec::Vec<(u16, u16)>, + >>::iter_prefix(NetUid::ROOT) { let mut modified_weights = weights_i.clone(); for (subnet_id, weight) in modified_weights.iter_mut() { @@ -449,6 +447,8 @@ impl Pallet { for (_uid, key) in keys { IsNetworkMember::::remove(key, netuid); } + + // --- 11. Core per-net parameters (already present + a few that were missing). Tempo::::remove(netuid); Kappa::::remove(netuid); Difficulty::::remove(netuid); @@ -460,27 +460,221 @@ impl Pallet { RegistrationsThisInterval::::remove(netuid); POWRegistrationsThisInterval::::remove(netuid); BurnRegistrationsThisInterval::::remove(netuid); + + // --- 12. AMM / price / accounting (expanded). SubnetTAO::::remove(netuid); SubnetAlphaInEmission::::remove(netuid); SubnetAlphaOutEmission::::remove(netuid); SubnetTaoInEmission::::remove(netuid); SubnetVolume::::remove(netuid); SubnetMovingPrice::::remove(netuid); + + // Additional AMM & pool surfaces that can exist independently of dissolve paths: + SubnetAlphaIn::::remove(netuid); + SubnetAlphaInProvided::::remove(netuid); + SubnetAlphaOut::::remove(netuid); + SubnetTaoProvided::::remove(netuid); + + // --- 13. Token / mechanism / registration toggles that were previously left behind. TokenSymbol::::remove(netuid); SubnetMechanism::::remove(netuid); SubnetOwnerHotkey::::remove(netuid); - SubnetOwner::::remove(netuid); - - // --- 11. Remove subnet identity if it exists. + NetworkRegistrationAllowed::::remove(netuid); + NetworkPowRegistrationAllowed::::remove(netuid); + + // --- 14. Locks & toggles. + TransferToggle::::remove(netuid); + SubnetLocked::::remove(netuid); + LargestLocked::::remove(netuid); + + // --- 15. Mechanism step / emissions bookkeeping. + FirstEmissionBlockNumber::::remove(netuid); + PendingEmission::::remove(netuid); + PendingRootDivs::::remove(netuid); + PendingAlphaSwapped::::remove(netuid); + PendingOwnerCut::::remove(netuid); + BlocksSinceLastStep::::remove(netuid); + LastMechansimStepBlock::::remove(netuid); + + // --- 16. Serving / rho / curves, and other per-net controls. + ServingRateLimit::::remove(netuid); + Rho::::remove(netuid); + AlphaSigmoidSteepness::::remove(netuid); + + MaxAllowedValidators::::remove(netuid); + AdjustmentInterval::::remove(netuid); + BondsMovingAverage::::remove(netuid); + BondsPenalty::::remove(netuid); + BondsResetOn::::remove(netuid); + WeightsSetRateLimit::::remove(netuid); + ValidatorPruneLen::::remove(netuid); + ScalingLawPower::::remove(netuid); + TargetRegistrationsPerInterval::::remove(netuid); + AdjustmentAlpha::::remove(netuid); + CommitRevealWeightsEnabled::::remove(netuid); + + Burn::::remove(netuid); + MinBurn::::remove(netuid); + MaxBurn::::remove(netuid); + MinDifficulty::::remove(netuid); + MaxDifficulty::::remove(netuid); + LastAdjustmentBlock::::remove(netuid); + RegistrationsThisBlock::::remove(netuid); + EMAPriceHalvingBlocks::::remove(netuid); + RAORecycledForRegistration::::remove(netuid); + MaxRegistrationsPerBlock::::remove(netuid); + WeightsVersionKey::::remove(netuid); + + // --- 17. Subtoken / feature flags. + LiquidAlphaOn::::remove(netuid); + Yuma3On::::remove(netuid); + AlphaValues::::remove(netuid); + SubtokenEnabled::::remove(netuid); + ImmuneOwnerUidsLimit::::remove(netuid); + + // --- 18. Consensus aux vectors. + StakeWeight::::remove(netuid); + LoadedEmission::::remove(netuid); + + // --- 19. DMAPs where netuid is the FIRST key: can clear by prefix. + let _ = BlockAtRegistration::::clear_prefix(netuid, u32::MAX, None); + let _ = Axons::::clear_prefix(netuid, u32::MAX, None); + let _ = NeuronCertificates::::clear_prefix(netuid, u32::MAX, None); + let _ = Prometheus::::clear_prefix(netuid, u32::MAX, None); + let _ = AlphaDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); + let _ = TaoDividendsPerSubnet::::clear_prefix(netuid, u32::MAX, None); + let _ = PendingChildKeys::::clear_prefix(netuid, u32::MAX, None); + let _ = AssociatedEvmAddress::::clear_prefix(netuid, u32::MAX, None); + + // Commit-reveal / weights commits (all per-net prefixes): + let _ = WeightCommits::::clear_prefix(netuid, u32::MAX, None); + let _ = TimelockedWeightCommits::::clear_prefix(netuid, u32::MAX, None); + let _ = CRV3WeightCommits::::clear_prefix(netuid, u32::MAX, None); + let _ = CRV3WeightCommitsV2::::clear_prefix(netuid, u32::MAX, None); + RevealPeriodEpochs::::remove(netuid); + + // Last hotkey swap (DMAP where netuid is FIRST key → easy) + let _ = LastHotkeySwapOnNetuid::::clear_prefix(netuid, u32::MAX, None); + + // --- 20. Identity maps across versions (netuid-scoped). + SubnetIdentities::::remove(netuid); + SubnetIdentitiesV2::::remove(netuid); if SubnetIdentitiesV3::::contains_key(netuid) { SubnetIdentitiesV3::::remove(netuid); Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); } - // --- Log final removal. + // --- 21. DMAP / NMAP where netuid is NOT the first key → iterate & remove. + + // ChildkeyTake: (hot, netuid) → u16 + { + let to_rm: sp_std::vec::Vec = ChildkeyTake::::iter() + .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) + .collect(); + for hot in to_rm { + ChildkeyTake::::remove(&hot, netuid); + } + } + // ChildKeys: (parent, netuid) → Vec<...> + { + let to_rm: sp_std::vec::Vec = ChildKeys::::iter() + .filter_map(|(parent, n, _)| if n == netuid { Some(parent) } else { None }) + .collect(); + for parent in to_rm { + ChildKeys::::remove(&parent, netuid); + } + } + // ParentKeys: (child, netuid) → Vec<...> + { + let to_rm: sp_std::vec::Vec = ParentKeys::::iter() + .filter_map(|(child, n, _)| if n == netuid { Some(child) } else { None }) + .collect(); + for child in to_rm { + ParentKeys::::remove(&child, netuid); + } + } + // LastHotkeyEmissionOnNetuid: (hot, netuid) → α + { + let to_rm: sp_std::vec::Vec = LastHotkeyEmissionOnNetuid::::iter() + .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) + .collect(); + for hot in to_rm { + LastHotkeyEmissionOnNetuid::::remove(&hot, netuid); + } + } + // TotalHotkeyAlpha / TotalHotkeyAlphaLastEpoch / TotalHotkeyShares: (hot, netuid) → ... + { + let to_rm_alpha: sp_std::vec::Vec = TotalHotkeyAlpha::::iter() + .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) + .collect(); + for hot in to_rm_alpha { + TotalHotkeyAlpha::::remove(&hot, netuid); + } + + let to_rm_alpha_last: sp_std::vec::Vec = + TotalHotkeyAlphaLastEpoch::::iter() + .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) + .collect(); + for hot in to_rm_alpha_last { + TotalHotkeyAlphaLastEpoch::::remove(&hot, netuid); + } + + let to_rm_shares: sp_std::vec::Vec = TotalHotkeyShares::::iter() + .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) + .collect(); + for hot in to_rm_shares { + TotalHotkeyShares::::remove(&hot, netuid); + } + } + // Alpha shares NMAP: (hot, cold, netuid) → U64F64 + { + let to_rm: sp_std::vec::Vec<(T::AccountId, T::AccountId)> = Alpha::::iter() + .filter_map( + |((hot, cold, n), _)| if n == netuid { Some((hot, cold)) } else { None }, + ) + .collect(); + for (hot, cold) in to_rm { + Alpha::::remove((hot, cold, netuid)); + } + } + // TransactionKeyLastBlock NMAP: (hot, netuid, name) → u64 + { + let to_rm: sp_std::vec::Vec<(T::AccountId, u16)> = TransactionKeyLastBlock::::iter() + .filter_map( + |((hot, n, name), _)| if n == netuid { Some((hot, name)) } else { None }, + ) + .collect(); + for (hot, name) in to_rm { + TransactionKeyLastBlock::::remove((hot, netuid, name)); + } + } + // StakingOperationRateLimiter NMAP: (hot, cold, netuid) → bool + { + let to_rm: sp_std::vec::Vec<(T::AccountId, T::AccountId)> = + StakingOperationRateLimiter::::iter() + .filter_map( + |((hot, cold, n), _)| { + if n == netuid { Some((hot, cold)) } else { None } + }, + ) + .collect(); + for (hot, cold) in to_rm { + StakingOperationRateLimiter::::remove((hot, cold, netuid)); + } + } + + // --- 22. Subnet leasing: remove mapping and any lease-scoped state linked to this netuid. + if let Some(lease_id) = SubnetUidToLeaseId::::take(netuid) { + SubnetLeases::::remove(lease_id); + let _ = SubnetLeaseShares::::clear_prefix(lease_id, u32::MAX, None); + AccumulatedLeaseDividends::::remove(lease_id); + } + + // --- Final removal logging. log::debug!( "remove_network: netuid={netuid}, owner={owner_coldkey:?} removed successfully" ); + Self::deposit_event(Event::NetworkRemoved(netuid)); } #[allow(clippy::arithmetic_side_effects)] diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 55eba699fd..d476e6eaa0 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -286,12 +286,15 @@ fn dissolve_clears_all_per_subnet_storages() { // ------------------------------------------------------------------ // Populate each storage item with a minimal value of the CORRECT type // ------------------------------------------------------------------ + // Core ownership / bookkeeping SubnetOwner::::insert(net, owner_cold); + SubnetOwnerHotkey::::insert(net, owner_hot); SubnetworkN::::insert(net, 0u16); NetworkModality::::insert(net, 0u16); NetworksAdded::::insert(net, true); NetworkRegisteredAt::::insert(net, 0u64); + // Consensus vectors Rank::::insert(net, vec![1u16]); Trust::::insert(net, vec![1u16]); Active::::insert(net, vec![true]); @@ -301,10 +304,10 @@ fn dissolve_clears_all_per_subnet_storages() { Dividends::::insert(net, vec![1u16]); PruningScores::::insert(net, vec![1u16]); LastUpdate::::insert(net, vec![0u64]); - ValidatorPermit::::insert(net, vec![true]); ValidatorTrust::::insert(net, vec![1u16]); + // Per‑net params Tempo::::insert(net, 1u16); Kappa::::insert(net, 1u16); Difficulty::::insert(net, 1u64); @@ -319,13 +322,14 @@ fn dissolve_clears_all_per_subnet_storages() { POWRegistrationsThisInterval::::insert(net, 1u16); BurnRegistrationsThisInterval::::insert(net, 1u16); + // Pool / AMM counters SubnetTAO::::insert(net, TaoCurrency::from(1)); SubnetAlphaInEmission::::insert(net, AlphaCurrency::from(1)); SubnetAlphaOutEmission::::insert(net, AlphaCurrency::from(1)); SubnetTaoInEmission::::insert(net, TaoCurrency::from(1)); SubnetVolume::::insert(net, 1u128); - // Fields that will be ZEROED (not removed) + // Items now REMOVED (not zeroed) by dissolution SubnetAlphaIn::::insert(net, AlphaCurrency::from(2)); SubnetAlphaOut::::insert(net, AlphaCurrency::from(3)); @@ -333,7 +337,97 @@ fn dissolve_clears_all_per_subnet_storages() { Keys::::insert(net, 0u16, owner_hot); Bonds::::insert(net, 0u16, vec![(0u16, 1u16)]); Weights::::insert(net, 0u16, vec![(1u16, 1u16)]); - IsNetworkMember::::insert(owner_cold, net, true); + + // Membership entry for the SAME hotkey as Keys + IsNetworkMember::::insert(owner_hot, net, true); + + + // Token / price / provided reserves + TokenSymbol::::insert(net, b"XX".to_vec()); + SubnetMovingPrice::::insert(net, substrate_fixed::types::I96F32::from_num(1)); + SubnetTaoProvided::::insert(net, TaoCurrency::from(1)); + SubnetAlphaInProvided::::insert(net, AlphaCurrency::from(1)); + + // Subnet locks + TransferToggle::::insert(net, true); + SubnetLocked::::insert(net, TaoCurrency::from(1)); + LargestLocked::::insert(net, 1u64); + + // Subnet parameters & pending counters + FirstEmissionBlockNumber::::insert(net, 1u64); + SubnetMechanism::::insert(net, 1u16); + NetworkRegistrationAllowed::::insert(net, true); + NetworkPowRegistrationAllowed::::insert(net, true); + PendingEmission::::insert(net, AlphaCurrency::from(1)); + PendingRootDivs::::insert(net, TaoCurrency::from(1)); + PendingAlphaSwapped::::insert(net, AlphaCurrency::from(1)); + PendingOwnerCut::::insert(net, AlphaCurrency::from(1)); + BlocksSinceLastStep::::insert(net, 1u64); + LastMechansimStepBlock::::insert(net, 1u64); + ServingRateLimit::::insert(net, 1u64); + Rho::::insert(net, 1u16); + AlphaSigmoidSteepness::::insert(net, 1i16); + + // Weights/versioning/targets/limits + WeightsVersionKey::::insert(net, 1u64); + MaxAllowedValidators::::insert(net, 1u16); + AdjustmentInterval::::insert(net, 2u16); + BondsMovingAverage::::insert(net, 1u64); + BondsPenalty::::insert(net, 1u16); + BondsResetOn::::insert(net, true); + WeightsSetRateLimit::::insert(net, 1u64); + ValidatorPruneLen::::insert(net, 1u64); + ScalingLawPower::::insert(net, 1u16); + TargetRegistrationsPerInterval::::insert(net, 1u16); + AdjustmentAlpha::::insert(net, 1u64); + CommitRevealWeightsEnabled::::insert(net, true); + + // Burn/difficulty/adjustment + Burn::::insert(net, TaoCurrency::from(1)); + MinBurn::::insert(net, TaoCurrency::from(1)); + MaxBurn::::insert(net, TaoCurrency::from(2)); + MinDifficulty::::insert(net, 1u64); + MaxDifficulty::::insert(net, 2u64); + LastAdjustmentBlock::::insert(net, 1u64); + RegistrationsThisBlock::::insert(net, 1u16); + EMAPriceHalvingBlocks::::insert(net, 1u64); + RAORecycledForRegistration::::insert(net, TaoCurrency::from(1)); + + // Feature toggles + LiquidAlphaOn::::insert(net, true); + Yuma3On::::insert(net, true); + AlphaValues::::insert(net, (1u16, 2u16)); + SubtokenEnabled::::insert(net, true); + ImmuneOwnerUidsLimit::::insert(net, 1u16); + + // Per‑subnet vectors / indexes + StakeWeight::::insert(net, vec![1u16]); + + // Uid/registration + Uids::::insert(net, owner_hot, 0u16); + BlockAtRegistration::::insert(net, 0u16, 1u64); + + // Per‑subnet dividends + AlphaDividendsPerSubnet::::insert(net, owner_hot, AlphaCurrency::from(1)); + TaoDividendsPerSubnet::::insert(net, owner_hot, TaoCurrency::from(1)); + + // Parent/child topology + takes + ChildkeyTake::::insert(owner_hot, net, 1u16); + PendingChildKeys::::insert(net, owner_cold, (vec![(1u64, owner_hot)], 1u64)); + ChildKeys::::insert(owner_cold, net, vec![(1u64, owner_hot)]); + ParentKeys::::insert(owner_hot, net, vec![(1u64, owner_cold)]); + + // Hotkey swap timestamp for subnet + LastHotkeySwapOnNetuid::::insert(net, owner_cold, 1u64); + + // Axon/prometheus tx key timing (NMap) — ***correct key-tuple insertion*** + TransactionKeyLastBlock::::insert((owner_hot, net, 1u16), 1u64); + + // EVM association indexed by (netuid, uid) + AssociatedEvmAddress::::insert(net, 0u16, (sp_core::H160::zero(), 1u64)); + + // (Optional) subnet -> lease link + SubnetUidToLeaseId::::insert(net, 42u32); // ------------------------------------------------------------------ // Dissolve @@ -344,11 +438,13 @@ fn dissolve_clears_all_per_subnet_storages() { // Items that must be COMPLETELY REMOVED // ------------------------------------------------------------------ assert!(!SubnetOwner::::contains_key(net)); + assert!(!SubnetOwnerHotkey::::contains_key(net)); assert!(!SubnetworkN::::contains_key(net)); assert!(!NetworkModality::::contains_key(net)); assert!(!NetworksAdded::::contains_key(net)); assert!(!NetworkRegisteredAt::::contains_key(net)); + // Consensus vectors removed assert!(!Rank::::contains_key(net)); assert!(!Trust::::contains_key(net)); assert!(!Active::::contains_key(net)); @@ -362,6 +458,7 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!ValidatorPermit::::contains_key(net)); assert!(!ValidatorTrust::::contains_key(net)); + // Per‑net params removed assert!(!Tempo::::contains_key(net)); assert!(!Kappa::::contains_key(net)); assert!(!Difficulty::::contains_key(net)); @@ -376,26 +473,117 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!POWRegistrationsThisInterval::::contains_key(net)); assert!(!BurnRegistrationsThisInterval::::contains_key(net)); + // Pool / AMM counters removed assert!(!SubnetTAO::::contains_key(net)); assert!(!SubnetAlphaInEmission::::contains_key(net)); assert!(!SubnetAlphaOutEmission::::contains_key(net)); assert!(!SubnetTaoInEmission::::contains_key(net)); assert!(!SubnetVolume::::contains_key(net)); - // ------------------------------------------------------------------ - // Items expected to be PRESENT but ZERO - // ------------------------------------------------------------------ - assert_eq!(SubnetAlphaIn::::get(net), 0.into()); - assert_eq!(SubnetAlphaOut::::get(net), 0.into()); + // These are now REMOVED + assert!(!SubnetAlphaIn::::contains_key(net)); + assert!(!SubnetAlphaOut::::contains_key(net)); - // ------------------------------------------------------------------ // Collections fully cleared - // ------------------------------------------------------------------ assert!(Keys::::iter_prefix(net).next().is_none()); assert!(Bonds::::iter_prefix(net).next().is_none()); assert!(Weights::::iter_prefix(net).next().is_none()); assert!(!IsNetworkMember::::contains_key(owner_hot, net)); + // Token / price / provided reserves + assert!(!TokenSymbol::::contains_key(net)); + assert!(!SubnetMovingPrice::::contains_key(net)); + assert!(!SubnetTaoProvided::::contains_key(net)); + assert!(!SubnetAlphaInProvided::::contains_key(net)); + + // Subnet locks + assert!(!TransferToggle::::contains_key(net)); + assert!(!SubnetLocked::::contains_key(net)); + assert!(!LargestLocked::::contains_key(net)); + + // Subnet parameters & pending counters + assert!(!FirstEmissionBlockNumber::::contains_key(net)); + assert!(!SubnetMechanism::::contains_key(net)); + assert!(!NetworkRegistrationAllowed::::contains_key(net)); + assert!(!NetworkPowRegistrationAllowed::::contains_key(net)); + assert!(!PendingEmission::::contains_key(net)); + assert!(!PendingRootDivs::::contains_key(net)); + assert!(!PendingAlphaSwapped::::contains_key(net)); + assert!(!PendingOwnerCut::::contains_key(net)); + assert!(!BlocksSinceLastStep::::contains_key(net)); + assert!(!LastMechansimStepBlock::::contains_key(net)); + assert!(!ServingRateLimit::::contains_key(net)); + assert!(!Rho::::contains_key(net)); + assert!(!AlphaSigmoidSteepness::::contains_key(net)); + + // Weights/versioning/targets/limits + assert!(!WeightsVersionKey::::contains_key(net)); + assert!(!MaxAllowedValidators::::contains_key(net)); + assert!(!AdjustmentInterval::::contains_key(net)); + assert!(!BondsMovingAverage::::contains_key(net)); + assert!(!BondsPenalty::::contains_key(net)); + assert!(!BondsResetOn::::contains_key(net)); + assert!(!WeightsSetRateLimit::::contains_key(net)); + assert!(!ValidatorPruneLen::::contains_key(net)); + assert!(!ScalingLawPower::::contains_key(net)); + assert!(!TargetRegistrationsPerInterval::::contains_key(net)); + assert!(!AdjustmentAlpha::::contains_key(net)); + assert!(!CommitRevealWeightsEnabled::::contains_key(net)); + + // Burn/difficulty/adjustment + assert!(!Burn::::contains_key(net)); + assert!(!MinBurn::::contains_key(net)); + assert!(!MaxBurn::::contains_key(net)); + assert!(!MinDifficulty::::contains_key(net)); + assert!(!MaxDifficulty::::contains_key(net)); + assert!(!LastAdjustmentBlock::::contains_key(net)); + assert!(!RegistrationsThisBlock::::contains_key(net)); + assert!(!EMAPriceHalvingBlocks::::contains_key(net)); + assert!(!RAORecycledForRegistration::::contains_key(net)); + + // Feature toggles + assert!(!LiquidAlphaOn::::contains_key(net)); + assert!(!Yuma3On::::contains_key(net)); + assert!(!AlphaValues::::contains_key(net)); + assert!(!SubtokenEnabled::::contains_key(net)); + assert!(!ImmuneOwnerUidsLimit::::contains_key(net)); + + // Per‑subnet vectors / indexes + assert!(!StakeWeight::::contains_key(net)); + + // Uid/registration + assert!(Uids::::get(net, owner_hot).is_none()); + assert!(!BlockAtRegistration::::contains_key(net, 0u16)); + + // Per‑subnet dividends + assert!(!AlphaDividendsPerSubnet::::contains_key( + net, owner_hot + )); + assert!(!TaoDividendsPerSubnet::::contains_key(net, owner_hot)); + + // Parent/child topology + takes + assert!(!ChildkeyTake::::contains_key(owner_hot, net)); + assert!(!PendingChildKeys::::contains_key(net, owner_cold)); + assert!(!ChildKeys::::contains_key(owner_cold, net)); + assert!(!ParentKeys::::contains_key(owner_hot, net)); + + // Hotkey swap timestamp for subnet + assert!(!LastHotkeySwapOnNetuid::::contains_key( + net, owner_cold + )); + + // Axon/prometheus tx key timing (NMap) — ValueQuery (defaults to 0) + assert_eq!( + TransactionKeyLastBlock::::get((owner_hot, net, 1u16)), + 0u64 + ); + + // EVM association + assert!(AssociatedEvmAddress::::get(net, 0u16).is_none()); + + // Subnet -> lease link + assert!(!SubnetUidToLeaseId::::contains_key(net)); + // ------------------------------------------------------------------ // Final subnet removal confirmation // ------------------------------------------------------------------ From 25ada7ca4e434bd67cb789e3f9a1c4df882502af Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:11:42 -0700 Subject: [PATCH 60/97] use NetworksAdded --- pallets/subtensor/src/subnets/subnet.rs | 5 ++++- pallets/subtensor/src/tests/networks.rs | 13 ++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 21df71e8d6..6749342230 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -149,7 +149,10 @@ impl Pallet { // --- 5. Check if we need to prune a subnet (if at SubnetLimit). // But do not prune yet; we only do it after all checks pass. let subnet_limit = Self::get_max_subnets(); - let current_count = TotalNetworks::::get(); + let current_count: u16 = NetworksAdded::::iter() + .filter(|(netuid, added)| *added && *netuid != NetUid::ROOT) + .count() as u16; + let mut recycle_netuid: Option = None; if current_count >= subnet_limit { if let Some(netuid) = Self::get_network_to_prune() { diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index d476e6eaa0..283cc4498f 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -228,13 +228,13 @@ fn dissolve_owner_cut_refund_logic() { // Current α→τ price for this subnet. let price: U96F32 = ::SwapInterface::current_alpha_price(net.into()); - let owner_emission_tau_u64: u64 = U96F32::from_num(owner_alpha_u64) + let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() .saturating_to_num::(); let expected_refund: TaoCurrency = - lock.saturating_sub(TaoCurrency::from(owner_emission_tau_u64)); + lock.saturating_sub(TaoCurrency::from(owner_emission_tao_u64)); let before = SubtensorModule::get_coldkey_balance(&oc); assert_ok!(SubtensorModule::do_dissolve_network(net)); @@ -341,7 +341,6 @@ fn dissolve_clears_all_per_subnet_storages() { // Membership entry for the SAME hotkey as Keys IsNetworkMember::::insert(owner_hot, net, true); - // Token / price / provided reserves TokenSymbol::::insert(net, b"XX".to_vec()); SubnetMovingPrice::::insert(net, substrate_fixed::types::I96F32::from_num(1)); @@ -1999,7 +1998,7 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( let [hot1, _hot2] = cold_to_hots[&cold]; register_ok_neuron(net_new, hot1, cold, 7777); - let before_tau = SubtensorModule::get_coldkey_balance(&cold); + let before_tao = SubtensorModule::get_coldkey_balance(&cold); let a_prev: u64 = Alpha::::get((hot1, cold, net_new)).saturating_to_num(); // Expected α for this exact τ, using the same sim path as the pallet. @@ -2018,14 +2017,14 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( min_amount_required.into() )); - let after_tau = SubtensorModule::get_coldkey_balance(&cold); + let after_tao = SubtensorModule::get_coldkey_balance(&cold); let a_new: u64 = Alpha::::get((hot1, cold, net_new)).saturating_to_num(); let a_delta = a_new.saturating_sub(a_prev); // τ decreased by exactly the amount we sent. assert_eq!( - after_tau, - before_tau.saturating_sub(min_amount_required), + after_tao, + before_tao.saturating_sub(min_amount_required), "τ did not decrease by the min required restake amount for cold {cold:?}" ); From b8913217ab124b453595413b4599199e0334c239 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:27:38 -0700 Subject: [PATCH 61/97] CannotAffordLockCost --- pallets/subtensor/src/macros/errors.rs | 2 ++ pallets/subtensor/src/subnets/subnet.rs | 4 ++-- pallets/subtensor/src/tests/networks.rs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 6378a525e4..8358ba9a46 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -248,5 +248,7 @@ mod errors { InvalidValue, /// Subnet limit reached & there is no eligible subnet to prune SubnetLimitReached, + /// Insufficient funds to meet the subnet lock cost + CannotAffordLockCost, } } diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 6749342230..e6be8b8cd5 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -116,7 +116,7 @@ impl Pallet { /// * `MechanismDoesNotExist` – unsupported `mechid`. /// * `NetworkTxRateLimitExceeded` – caller hit the register-network rate limit. /// * `SubnetLimitReached` – limit hit **and** no eligible subnet to prune. - /// * `NotEnoughBalanceToStake` – caller lacks the lock cost. + /// * `CannotAffordLockCost` – caller lacks the lock cost. /// * `BalanceWithdrawalError` – failed to lock balance. /// * `InvalidIdentity` – supplied `identity` failed validation. /// @@ -167,7 +167,7 @@ impl Pallet { log::debug!("network lock_amount: {lock_amount:?}"); ensure!( Self::can_remove_balance_from_coldkey_account(&coldkey, lock_amount.into()), - Error::::NotEnoughBalanceToStake + Error::::CannotAffordLockCost ); // --- 7. Perform the lock operation. diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 283cc4498f..914a19e230 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -1245,7 +1245,7 @@ fn register_network_fails_before_prune_keeps_existing() { 1, None, ), - Error::::NotEnoughBalanceToStake + Error::::CannotAffordLockCost ); assert!(SubtensorModule::if_subnet_exist(net)); From 1ffe46ae75247ca7e4fd4761fc8197da0bd98fe6 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 24 Aug 2025 10:51:02 -0700 Subject: [PATCH 62/97] add more maps --- pallets/subtensor/src/subnets/subnet.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index e6be8b8cd5..1a76c92a40 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -104,7 +104,7 @@ impl Pallet { /// * **`origin`** – `T::RuntimeOrigin`  Must be **signed** by the coldkey. /// * **`hotkey`** – `&T::AccountId`  First neuron of the new subnet. /// * **`mechid`** – `u16`  Only the dynamic mechanism (`1`) is currently supported. - /// * **`identity`** – `Option`  Optional metadata for the subnet. + /// * **`identity`** – `Option`  Optional metadata for the subnet. /// /// ### Events /// * `NetworkAdded(netuid, mechid)` – always. @@ -218,11 +218,33 @@ impl Pallet { let pool_initial_alpha = AlphaCurrency::from(Self::get_network_min_lock().to_u64()); let actual_tao_lock_amount_less_pool_tao = actual_tao_lock_amount.saturating_sub(pool_initial_tao); + + // Core pool + ownership SubnetTAO::::insert(netuid_to_register, pool_initial_tao); SubnetAlphaIn::::insert(netuid_to_register, pool_initial_alpha); SubnetOwner::::insert(netuid_to_register, coldkey.clone()); SubnetOwnerHotkey::::insert(netuid_to_register, hotkey.clone()); + // ----- NEW: Make registration defaults explicit to mirror de‑registration cleanup ----- + // Transfer gating and lock accounting + TransferToggle::::insert(netuid_to_register, true); + SubnetLocked::::insert(netuid_to_register, pool_initial_tao); + LargestLocked::::insert(netuid_to_register, pool_initial_tao.to_u64()); + + // User‑provided reserves (liquidity) and out‑supply baselines + SubnetTaoProvided::::insert(netuid_to_register, TaoCurrency::ZERO); + SubnetAlphaInProvided::::insert(netuid_to_register, AlphaCurrency::from(0)); + SubnetAlphaOut::::insert(netuid_to_register, AlphaCurrency::from(0)); + + // Market telemetry baselines + SubnetVolume::::insert(netuid_to_register, 0u128); + + // Track burned/recycled amount for this registration + RAORecycledForRegistration::::insert( + netuid_to_register, + actual_tao_lock_amount_less_pool_tao, + ); + if actual_tao_lock_amount_less_pool_tao > TaoCurrency::ZERO { Self::burn_tokens(actual_tao_lock_amount_less_pool_tao); } From c6b3af60ace8856231584c4ef1de954774def632 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 24 Aug 2025 11:18:03 -0700 Subject: [PATCH 63/97] cleanup --- pallets/subtensor/src/subnets/subnet.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 1a76c92a40..4581d5cfd6 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -211,11 +211,10 @@ impl Pallet { let symbol = Self::get_next_available_symbol(netuid_to_register); TokenSymbol::::insert(netuid_to_register, symbol); - // Put initial TAO from lock into subnet TAO and produce numerically equal amount of Alpha - // The initial TAO is the locked amount, with a minimum of 1 RAO and a cap of 100 TAO. - let pool_initial_tao = Self::get_network_min_lock(); - // FIXME: the result from function is used as a mixed type alpha/tao - let pool_initial_alpha = AlphaCurrency::from(Self::get_network_min_lock().to_u64()); + // The initial TAO is the locked amount + // Put initial TAO from lock into subnet TAO and produce numerically equal amount of Alpha. + let pool_initial_tao: TaoCurrency = Self::get_network_min_lock(); + let pool_initial_alpha: AlphaCurrency = pool_initial_tao.to_u64().into(); let actual_tao_lock_amount_less_pool_tao = actual_tao_lock_amount.saturating_sub(pool_initial_tao); @@ -224,22 +223,13 @@ impl Pallet { SubnetAlphaIn::::insert(netuid_to_register, pool_initial_alpha); SubnetOwner::::insert(netuid_to_register, coldkey.clone()); SubnetOwnerHotkey::::insert(netuid_to_register, hotkey.clone()); - - // ----- NEW: Make registration defaults explicit to mirror de‑registration cleanup ----- - // Transfer gating and lock accounting TransferToggle::::insert(netuid_to_register, true); SubnetLocked::::insert(netuid_to_register, pool_initial_tao); LargestLocked::::insert(netuid_to_register, pool_initial_tao.to_u64()); - - // User‑provided reserves (liquidity) and out‑supply baselines SubnetTaoProvided::::insert(netuid_to_register, TaoCurrency::ZERO); SubnetAlphaInProvided::::insert(netuid_to_register, AlphaCurrency::from(0)); SubnetAlphaOut::::insert(netuid_to_register, AlphaCurrency::from(0)); - - // Market telemetry baselines SubnetVolume::::insert(netuid_to_register, 0u128); - - // Track burned/recycled amount for this registration RAORecycledForRegistration::::insert( netuid_to_register, actual_tao_lock_amount_less_pool_tao, @@ -302,6 +292,8 @@ impl Pallet { Self::set_immunity_period(netuid, 5000); Self::set_min_difficulty(netuid, u64::MAX); Self::set_max_difficulty(netuid, u64::MAX); + Self::set_commit_reveal_weights_enabled(netuid, true); + Self::set_yuma3_enabled(netuid, true); // Make network parameters explicit. if !Tempo::::contains_key(netuid) { From e3ac2e45f9d1dffd5c7ff342af7b657c5a6d20b0 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 24 Aug 2025 12:27:52 -0700 Subject: [PATCH 64/97] revert breaks too many tests --- pallets/subtensor/src/subnets/subnet.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 4581d5cfd6..522e267f3c 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -292,8 +292,6 @@ impl Pallet { Self::set_immunity_period(netuid, 5000); Self::set_min_difficulty(netuid, u64::MAX); Self::set_max_difficulty(netuid, u64::MAX); - Self::set_commit_reveal_weights_enabled(netuid, true); - Self::set_yuma3_enabled(netuid, true); // Make network parameters explicit. if !Tempo::::contains_key(netuid) { From 42a5815e8d89d67796f2de8fea97add78d45793f Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 24 Aug 2025 15:26:01 -0700 Subject: [PATCH 65/97] improve refund_alpha --- pallets/swap/src/pallet/impls.rs | 119 ++++++++++++++++--------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index ab4d1e8af8..fb86c69e2b 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1214,84 +1214,85 @@ impl Pallet { } /// Distribute `alpha_total` back to the coldkey's hotkeys for `netuid`. - /// - If the coldkey owns multiple hotkeys, split pro‑rata by current α stake on this subnet. - /// If all stakes are zero, split evenly. - /// - If no hotkeys exist, fall back to (coldkey, coldkey). + /// - Pro‑rata by current α stake on this subnet; if all zero, split evenly. + /// - Deterministic "largest remainders" rounding to ensure exact conservation. + /// - Robust to partial deposit failures: retries across successes, final fallback to (cold, cold). pub fn refund_alpha(netuid: NetUid, coldkey: &T::AccountId, alpha_total: AlphaCurrency) { if alpha_total.is_zero() { return; } - // 1) Fetch owned hotkeys via SubnetInfo; no direct dependency on pallet_subtensor. + // 1) Recipient set let mut hotkeys: sp_std::vec::Vec = T::SubnetInfo::get_owned_hotkeys(coldkey); - - // Fallback: if no hotkeys are currently owned, use coldkey as its own hotkey. if hotkeys.is_empty() { hotkeys.push(coldkey.clone()); } - // 2) Build weights based on current α stake on this subnet. - // If sum_weights == 0, we'll split evenly. + // 2) Weights = current α stake per hotkey; if all zero -> even split let weights: sp_std::vec::Vec = hotkeys .iter() - .map(|hk| { - let bal = T::BalanceOps::alpha_balance(netuid, coldkey, hk); - u128::from(bal.to_u64()) - }) + .map(|hk| u128::from(T::BalanceOps::alpha_balance(netuid, coldkey, hk).to_u64())) .collect(); let sum_weights: u128 = weights .iter() .copied() .fold(0u128, |acc, w| acc.saturating_add(w)); - let n: u128 = u128::from(hotkeys.len() as u64); - - let total_alpha_u128: u128 = u128::from(alpha_total.to_u64()); + let total_u128: u128 = u128::from(alpha_total.to_u64()); + let n = hotkeys.len(); - // 3) Compute integer shares with remainder handling. - let mut shares: sp_std::vec::Vec<(T::AccountId, u64)> = - sp_std::vec::Vec::with_capacity(hotkeys.len()); + // (account, planned_amount_u64) + let mut shares: sp_std::vec::Vec<(T::AccountId, u64)> = sp_std::vec::Vec::with_capacity(n); if sum_weights > 0 { - // Pro‑rata by weights. - let mut assigned: u128 = 0; - for (hk, w) in hotkeys.iter().cloned().zip(weights.iter().copied()) { - let numerator = total_alpha_u128.saturating_mul(w); - let part: u128 = numerator.checked_div(sum_weights).unwrap_or(0); - shares.push((hk, u64::try_from(part).unwrap_or(u64::MAX))); - assigned = assigned.saturating_add(part); + // 3a) Pro‑rata base + largest remainders (deterministic) + let mut bases: sp_std::vec::Vec = sp_std::vec::Vec::with_capacity(n); + let mut remainders: sp_std::vec::Vec<(usize, u128)> = + sp_std::vec::Vec::with_capacity(n); + + let mut base_sum: u128 = 0; + for (i, (&w, hk)) in weights.iter().zip(hotkeys.iter()).enumerate() { + let numer = total_u128.saturating_mul(w); + let base = numer.checked_div(sum_weights).unwrap_or(0); + let rem = numer.checked_rem(sum_weights).unwrap_or(0); + bases.push(base); + remainders.push((i, rem)); + base_sum = base_sum.saturating_add(base); + shares.push((hk.clone(), u64::try_from(base).unwrap_or(u64::MAX))); } - // Distribute remainder one‑by‑one. - let mut remainder: u128 = total_alpha_u128.saturating_sub(assigned); - let mut i: usize = 0; - while remainder > 0 && i < shares.len() { - if let Some(pair) = shares.get_mut(i) { - pair.1 = pair.1.saturating_add(1); - remainder = remainder.saturating_sub(1); - i = i.saturating_add(1); - } else { - break; + // Distribute leftover ones to the largest remainders; tie‑break by index for determinism + let mut leftover = total_u128.saturating_sub(base_sum); + if leftover > 0 { + remainders.sort_by(|a, b| { + // Descending by remainder, then ascending by index + b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)) + }); + let mut k = 0usize; + while leftover > 0 && k < remainders.len() { + let idx = remainders[k].0; + if let Some((_, amt)) = shares.get_mut(idx) { + *amt = amt.saturating_add(1); + } + leftover = leftover.saturating_sub(1); + k = k.saturating_add(1); } } } else { - // Even split. - let base_u128 = total_alpha_u128.checked_div(n).unwrap_or(0); - let mut remainder_u128 = total_alpha_u128.checked_rem(n).unwrap_or(0); - - let base: u64 = u64::try_from(base_u128).unwrap_or(u64::MAX); - for hk in hotkeys.into_iter() { - let add_one: u64 = if remainder_u128 > 0 { - remainder_u128 = remainder_u128.saturating_sub(1); - 1 - } else { - 0 - }; - shares.push((hk, base.saturating_add(add_one))); + // 3b) Even split with deterministic round‑robin remainder + let base = total_u128.checked_div(n as u128).unwrap_or(0); + let mut rem = total_u128.checked_rem(n as u128).unwrap_or(0); + for hk in hotkeys.iter() { + let mut amt = u64::try_from(base).unwrap_or(u64::MAX); + if rem > 0 { + amt = amt.saturating_add(1); + rem = rem.saturating_sub(1); + } + shares.push((hk.clone(), amt)); } } - // 4) Deposit to (coldkey, hotkey). On failure, collect leftover and retry on successes. + // 4) Deposit to (coldkey, each hotkey). Track leftover if any deposit fails. let mut leftover: u64 = 0; let mut successes: sp_std::vec::Vec = sp_std::vec::Vec::new(); @@ -1304,7 +1305,7 @@ impl Pallet { Ok(_) => successes.push(hk.clone()), Err(e) => { log::warn!( - "refund_alpha_to_hotkeys: increase_stake failed (cold={coldkey:?}, hot={hk:?}, netuid={netuid:?}, amt={amt_u64:?}): {e:?}" + "refund_alpha: increase_stake failed (cold={coldkey:?}, hot={hk:?}, netuid={netuid:?}, amt={amt_u64:?}): {e:?}" ); leftover = leftover.saturating_add(*amt_u64); } @@ -1313,10 +1314,11 @@ impl Pallet { // 5) Retry: spread any leftover across the hotkeys that succeeded in step 4. if leftover > 0 && !successes.is_empty() { - let count_u64 = successes.len() as u64; - let base = leftover.checked_div(count_u64).unwrap_or(0); - let mut rem = leftover.checked_rem(count_u64).unwrap_or(0); + let count = successes.len() as u64; + let base = leftover.checked_div(count).unwrap_or(0); + let mut rem = leftover.checked_rem(count).unwrap_or(0); + let mut leftover_retry: u64 = 0; for hk in successes.iter() { let add: u64 = base.saturating_add(if rem > 0 { rem = rem.saturating_sub(1); @@ -1327,12 +1329,17 @@ impl Pallet { if add == 0 { continue; } - let _ = T::BalanceOps::increase_stake(coldkey, hk, netuid, add.into()); + if let Err(e) = T::BalanceOps::increase_stake(coldkey, hk, netuid, add.into()) { + log::warn!( + "refund_alpha(retry): increase_stake failed (cold={coldkey:?}, hot={hk:?}, netuid={netuid:?}, amt={add:?}): {e:?}" + ); + leftover_retry = leftover_retry.saturating_add(add); + } } - leftover = 0; + leftover = leftover_retry; } - // 6) Final fallback: if for some reason every deposit failed, deposit to (coldkey, coldkey). + // 6) Final fallback: deposit any remainder to (coldkey, coldkey). if leftover > 0 { let _ = T::BalanceOps::increase_stake(coldkey, coldkey, netuid, leftover.into()); } From 70ec2ee9ae7fd60a204ea61da03d6852127ccc86 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 24 Aug 2025 15:48:13 -0700 Subject: [PATCH 66/97] add more dissolve LP tests --- pallets/swap/src/pallet/tests.rs | 189 +++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index e6148ed94e..863438c627 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -2276,3 +2276,192 @@ fn liquidate_v3_refunds_user_funds_and_clears_state() { assert!(!SwapV3Initialized::::contains_key(netuid)); }); } + +#[test] +fn refund_alpha_single_provider_exact() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(11); + let cold = OK_COLDKEY_ACCOUNT_ID; + let hot = OK_HOTKEY_ACCOUNT_ID; + + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // --- Create an alpha‑only position (range entirely above current tick → TAO = 0, ALPHA > 0). + let ct = CurrentTick::::get(netuid); + let tick_low = ct.next().expect("current tick should not be MAX in tests"); + let tick_high = TickIndex::MAX; + + let liquidity = 1_000_000_u64; + let (_pos_id, tao_needed, alpha_needed) = + Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) + .expect("add alpha-only liquidity"); + assert_eq!(tao_needed, 0, "alpha‑only position must not require TAO"); + assert!(alpha_needed > 0, "alpha‑only position must require ALPHA"); + + // --- Snapshot BEFORE we withdraw funds (baseline for conservation). + let alpha_before_hot = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); + let alpha_before_owner = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let alpha_before_total = alpha_before_hot + alpha_before_owner; + + // --- Mimic extrinsic bookkeeping: withdraw α and record provided reserve. + let alpha_taken = ::BalanceOps::decrease_stake( + &cold, + &hot, + netuid.into(), + alpha_needed.into(), + ) + .expect("decrease ALPHA"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), alpha_taken); + + // --- Act: dissolve (calls refund_alpha inside). + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // --- Assert: refunded back to the owner (may credit to (cold,cold)). + let alpha_after_hot = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); + let alpha_after_owner = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let alpha_after_total = alpha_after_hot + alpha_after_owner; + assert_eq!( + alpha_after_total, alpha_before_total, + "ALPHA principal must be conserved to the owner" + ); + + // --- State is cleared. + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert_eq!(Pallet::::count_positions(netuid, &cold), 0); + assert!(!SwapV3Initialized::::contains_key(netuid)); + }); +} + +#[test] +fn refund_alpha_multiple_providers_proportional_to_principal() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(12); + let c1 = OK_COLDKEY_ACCOUNT_ID; + let h1 = OK_HOTKEY_ACCOUNT_ID; + let c2 = OK_COLDKEY_ACCOUNT_ID_2; + let h2 = OK_HOTKEY_ACCOUNT_ID_2; + + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Use the same "above current tick" trick for alpha‑only positions. + let ct = CurrentTick::::get(netuid); + let tick_low = ct.next().expect("current tick should not be MAX in tests"); + let tick_high = TickIndex::MAX; + + // Provider #1 (smaller α) + let liq1 = 700_000_u64; + let (_p1, t1, a1) = + Pallet::::do_add_liquidity(netuid, &c1, &h1, tick_low, tick_high, liq1) + .expect("add alpha-only liquidity #1"); + assert_eq!(t1, 0); + assert!(a1 > 0); + + // Provider #2 (larger α) + let liq2 = 2_100_000_u64; + let (_p2, t2, a2) = + Pallet::::do_add_liquidity(netuid, &c2, &h2, tick_low, tick_high, liq2) + .expect("add alpha-only liquidity #2"); + assert_eq!(t2, 0); + assert!(a2 > 0); + + // Baselines BEFORE withdrawing + let a1_before_hot = ::BalanceOps::alpha_balance(netuid.into(), &c1, &h1); + let a1_before_owner = ::BalanceOps::alpha_balance(netuid.into(), &c1, &c1); + let a1_before = a1_before_hot + a1_before_owner; + + let a2_before_hot = ::BalanceOps::alpha_balance(netuid.into(), &c2, &h2); + let a2_before_owner = ::BalanceOps::alpha_balance(netuid.into(), &c2, &c2); + let a2_before = a2_before_hot + a2_before_owner; + + // Withdraw α and account reserves for each provider. + let a1_taken = + ::BalanceOps::decrease_stake(&c1, &h1, netuid.into(), a1.into()) + .expect("decrease α #1"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), a1_taken); + + let a2_taken = + ::BalanceOps::decrease_stake(&c2, &h2, netuid.into(), a2.into()) + .expect("decrease α #2"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), a2_taken); + + // Act + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // Each owner is restored to their exact baseline. + let a1_after_hot = ::BalanceOps::alpha_balance(netuid.into(), &c1, &h1); + let a1_after_owner = ::BalanceOps::alpha_balance(netuid.into(), &c1, &c1); + let a1_after = a1_after_hot + a1_after_owner; + assert_eq!( + a1_after, a1_before, + "owner #1 must receive their α principal back" + ); + + let a2_after_hot = ::BalanceOps::alpha_balance(netuid.into(), &c2, &h2); + let a2_after_owner = ::BalanceOps::alpha_balance(netuid.into(), &c2, &c2); + let a2_after = a2_after_hot + a2_after_owner; + assert_eq!( + a2_after, a2_before, + "owner #2 must receive their α principal back" + ); + }); +} + +#[test] +fn refund_alpha_same_cold_multiple_hotkeys_conserved_to_owner() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(13); + let cold = OK_COLDKEY_ACCOUNT_ID; + let hot1 = OK_HOTKEY_ACCOUNT_ID; + let hot2 = OK_HOTKEY_ACCOUNT_ID_2; + + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Two alpha‑only positions on different hotkeys of the same owner. + let ct = CurrentTick::::get(netuid); + let tick_low = ct.next().expect("current tick should not be MAX in tests"); + let tick_high = TickIndex::MAX; + + let (_p1, _t1, a1) = + Pallet::::do_add_liquidity(netuid, &cold, &hot1, tick_low, tick_high, 900_000) + .expect("add alpha-only pos (hot1)"); + let (_p2, _t2, a2) = + Pallet::::do_add_liquidity(netuid, &cold, &hot2, tick_low, tick_high, 1_500_000) + .expect("add alpha-only pos (hot2)"); + assert!(a1 > 0 && a2 > 0); + + // Baseline BEFORE: sum over (cold,hot1) + (cold,hot2) + (cold,cold). + let before_hot1 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot1); + let before_hot2 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot2); + let before_owner = ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let before_total = before_hot1 + before_hot2 + before_owner; + + // Withdraw α from both hotkeys; track provided‑reserve. + let t1 = + ::BalanceOps::decrease_stake(&cold, &hot1, netuid.into(), a1.into()) + .expect("decr α #hot1"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), t1); + + let t2 = + ::BalanceOps::decrease_stake(&cold, &hot2, netuid.into(), a2.into()) + .expect("decr α #hot2"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), t2); + + // Act + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // The total α "owned" by the coldkey is conserved (credit may land on (cold,cold)). + let after_hot1 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot1); + let after_hot2 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot2); + let after_owner = ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let after_total = after_hot1 + after_hot2 + after_owner; + + assert_eq!( + after_total, before_total, + "owner’s α must be conserved across hot ledgers + (owner,owner)" + ); + }); +} From 8d838c443f71b9e77d0cdbf3014c6a22fdde4ce5 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 24 Aug 2025 16:15:52 -0700 Subject: [PATCH 67/97] use moving price --- pallets/subtensor/src/coinbase/root.rs | 2 +- pallets/subtensor/src/tests/networks.rs | 62 +++++++++---------------- 2 files changed, 23 insertions(+), 41 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index b37d655d6f..1e4be4a212 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -799,7 +799,7 @@ impl Pallet { continue; } - let price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); + let price: U96F32 = Self::get_moving_alpha_price(netuid); // If tie on price, earliest registration wins. if price < candidate_price diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 914a19e230..4c00266587 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -3,7 +3,6 @@ use crate::migrations::migrate_network_immunity_period; use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; -use pallet_subtensor_swap::{AlphaSqrtPrice, SwapV3Initialized}; use sp_core::U256; use sp_std::collections::btree_map::BTreeMap; use substrate_fixed::types::{I96F32, U64F64, U96F32}; @@ -1007,38 +1006,22 @@ fn prune_selection_complex_state_exhaustive() { System::set_block_number(imm + 6); let n6 = add_dynamic_network(&U256::from(106), &U256::from(206)); // immune at first - // (Root is ignored by the selector; we may still set it for completeness.) + // (Root is ignored by the selector.) let root = NetUid::ROOT; // --------------------------------------------------------------------- - // Drive price via V3 sqrt-price path: price = (sqrt_price)^2 - // Ensure V3 is initialized so current_alpha_price() uses sqrt-price. + // Drive pruning via the EMA/moving price used by `get_network_to_prune()`. + // We set the moving prices directly to create deterministic selections. + // + // Intended prices: + // n1: 25, n2: 25, n3: 100, n4: 1, n5: 0 (immune initially), n6: 0 (immune initially) // --------------------------------------------------------------------- - for net in [n1, n2, n3, n4, n5, n6] { - assert_ok!( - pallet_subtensor_swap::Pallet::::toggle_user_liquidity( - RuntimeOrigin::root(), - net, - true - ) - ); - SwapV3Initialized::::insert(net, true); - } - - // sqrt prices → prices: - // n1: sqrt=5 → price 25 - // n2: sqrt=5 → price 25 - // n3: sqrt=10 → price 100 - // n4: sqrt=1 → price 1 (lowest among matured initially) - // n5: sqrt=0 → price 0 (lowest overall but immune initially) - // n6: sqrt=0 → price 0 (lowest overall but immune initially) - AlphaSqrtPrice::::insert(n1, U64F64::from_num(5)); - AlphaSqrtPrice::::insert(n2, U64F64::from_num(5)); - AlphaSqrtPrice::::insert(n3, U64F64::from_num(10)); - AlphaSqrtPrice::::insert(n4, U64F64::from_num(1)); - AlphaSqrtPrice::::insert(n5, U64F64::from_num(0)); - AlphaSqrtPrice::::insert(n6, U64F64::from_num(0)); - AlphaSqrtPrice::::insert(root, U64F64::from_num(0)); + SubnetMovingPrice::::insert(n1, I96F32::from_num(25)); + SubnetMovingPrice::::insert(n2, I96F32::from_num(25)); + SubnetMovingPrice::::insert(n3, I96F32::from_num(100)); + SubnetMovingPrice::::insert(n4, I96F32::from_num(1)); + SubnetMovingPrice::::insert(n5, I96F32::from_num(0)); + SubnetMovingPrice::::insert(n6, I96F32::from_num(0)); // --------------------------------------------------------------------- // Phase A: Only n1..n4 are mature → lowest price (n4=1) should win. @@ -1052,11 +1035,11 @@ fn prune_selection_complex_state_exhaustive() { // --------------------------------------------------------------------- // Phase B: Tie on price with *same registration time* (n1 vs n2). - // Raise n4's price to 25 (sqrt=5) so {n1=25, n2=25, n3=100, n4=25}. + // Raise n4's price to 25 so {n1=25, n2=25, n3=100, n4=25}. // n1 and n2 share the *same registered_at*. The tie should keep the // first encountered (stable iteration by key order) → n1. // --------------------------------------------------------------------- - AlphaSqrtPrice::::insert(n4, U64F64::from_num(5)); // price now 25 + SubnetMovingPrice::::insert(n4, I96F32::from_num(25)); // n4 now 25 assert_eq!( SubtensorModule::get_network_to_prune(), Some(n1), @@ -1065,10 +1048,10 @@ fn prune_selection_complex_state_exhaustive() { // --------------------------------------------------------------------- // Phase C: Tie on price with *different registration times*. - // Make n3 price=25 as well (sqrt=5). Now n1,n2,n3,n4 all have price=25. - // Earliest registration time among them is n1 (block 0). + // Make n3 price=25 as well. Now n1,n2,n3,n4 all have price=25. + // Earliest registration among them is n1 (block 0). // --------------------------------------------------------------------- - AlphaSqrtPrice::::insert(n3, U64F64::from_num(5)); // price now 25 + SubnetMovingPrice::::insert(n3, I96F32::from_num(25)); assert_eq!( SubtensorModule::get_network_to_prune(), Some(n1), @@ -1140,10 +1123,10 @@ fn prune_selection_complex_state_exhaustive() { // --------------------------------------------------------------------- // Phase H: Dynamic price changes. - // Make n6 expensive (sqrt=10 → price=100); make n3 cheapest (sqrt=1 → price=1). + // Make n6 expensive (price 100); make n3 cheapest (price 1). // --------------------------------------------------------------------- - AlphaSqrtPrice::::insert(n6, U64F64::from_num(10)); // price 100 - AlphaSqrtPrice::::insert(n3, U64F64::from_num(1)); // price 1 + SubnetMovingPrice::::insert(n6, I96F32::from_num(100)); + SubnetMovingPrice::::insert(n3, I96F32::from_num(1)); assert_eq!( SubtensorModule::get_network_to_prune(), Some(n3), @@ -1155,7 +1138,7 @@ fn prune_selection_complex_state_exhaustive() { // Give n2 the same price as n3; n2 registered at block 0, n3 at block 1. // n2 should be chosen. // --------------------------------------------------------------------- - AlphaSqrtPrice::::insert(n2, U64F64::from_num(1)); // price 1 + SubnetMovingPrice::::insert(n2, I96F32::from_num(1)); assert_eq!( SubtensorModule::get_network_to_prune(), Some(n2), @@ -1174,8 +1157,7 @@ fn prune_selection_complex_state_exhaustive() { ); NetworksAdded::::insert(n2, true); - // Root is always ignored even if cheapest. - AlphaSqrtPrice::::insert(root, U64F64::from_num(0)); + // Root is always ignored even if cheapest (get_moving_alpha_price returns 1 for ROOT). assert_ne!( SubtensorModule::get_network_to_prune(), Some(root), From 53d7dbc7d90f5e1386f48938c087963b44860bc8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 25 Aug 2025 01:14:00 +0000 Subject: [PATCH 68/97] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 190e9d6012..4c891828a4 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -585,7 +585,7 @@ mod dispatches { /// - Errors stemming from transaction pallet. /// #[pallet::call_index(2)] - #[pallet::weight((Weight::from_parts(340_400_000, 0) + #[pallet::weight((Weight::from_parts(414_200_000, 0) .saturating_add(T::DbWeight::get().reads(26)) .saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::Yes))] pub fn add_stake( @@ -1195,7 +1195,7 @@ mod dispatches { #[pallet::call_index(59)] #[pallet::weight((Weight::from_parts(235_400_000, 0) .saturating_add(T::DbWeight::get().reads(37_u64)) - .saturating_add(T::DbWeight::get().writes(51_u64)), DispatchClass::Normal, Pays::No))] + .saturating_add(T::DbWeight::get().writes(59_u64)), DispatchClass::Normal, Pays::No))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) } @@ -1540,7 +1540,7 @@ mod dispatches { #[pallet::call_index(79)] #[pallet::weight((Weight::from_parts(234_200_000, 0) .saturating_add(T::DbWeight::get().reads(36_u64)) - .saturating_add(T::DbWeight::get().writes(50_u64)), DispatchClass::Normal, Pays::No))] + .saturating_add(T::DbWeight::get().writes(58_u64)), DispatchClass::Normal, Pays::No))] pub fn register_network_with_identity( origin: OriginFor, hotkey: T::AccountId, @@ -2156,7 +2156,7 @@ mod dispatches { /// Emits a `SymbolUpdated` event on success. #[pallet::call_index(112)] #[pallet::weight(( - Weight::from_parts(26_880_000, 0).saturating_add(T::DbWeight::get().reads_writes(4, 1)), + Weight::from_parts(46_040_000, 0).saturating_add(T::DbWeight::get().reads_writes(4, 1)), DispatchClass::Operational, Pays::Yes ))] From 5c46edb364f4e70415ec562da486dab7f83241a3 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:01:14 -0700 Subject: [PATCH 69/97] remove duplicates --- pallets/subtensor/src/coinbase/root.rs | 44 ++++---------------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 1e4be4a212..089b02d910 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -448,7 +448,7 @@ impl Pallet { IsNetworkMember::::remove(key, netuid); } - // --- 11. Core per-net parameters (already present + a few that were missing). + // --- 11. Core per-net parameters. Tempo::::remove(netuid); Kappa::::remove(netuid); Difficulty::::remove(netuid); @@ -461,21 +461,16 @@ impl Pallet { POWRegistrationsThisInterval::::remove(netuid); BurnRegistrationsThisInterval::::remove(netuid); - // --- 12. AMM / price / accounting (expanded). - SubnetTAO::::remove(netuid); + // --- 12. AMM / price / accounting. + // SubnetTAO, SubnetAlpha{In,InProvided,Out} are already cleared during dissolve/destroy. SubnetAlphaInEmission::::remove(netuid); SubnetAlphaOutEmission::::remove(netuid); SubnetTaoInEmission::::remove(netuid); SubnetVolume::::remove(netuid); SubnetMovingPrice::::remove(netuid); - - // Additional AMM & pool surfaces that can exist independently of dissolve paths: - SubnetAlphaIn::::remove(netuid); - SubnetAlphaInProvided::::remove(netuid); - SubnetAlphaOut::::remove(netuid); SubnetTaoProvided::::remove(netuid); - // --- 13. Token / mechanism / registration toggles that were previously left behind. + // --- 13. Token / mechanism / registration toggles. TokenSymbol::::remove(netuid); SubnetMechanism::::remove(netuid); SubnetOwnerHotkey::::remove(netuid); @@ -536,7 +531,7 @@ impl Pallet { StakeWeight::::remove(netuid); LoadedEmission::::remove(netuid); - // --- 19. DMAPs where netuid is the FIRST key: can clear by prefix. + // --- 19. DMAPs where netuid is the FIRST key: clear by prefix. let _ = BlockAtRegistration::::clear_prefix(netuid, u32::MAX, None); let _ = Axons::::clear_prefix(netuid, u32::MAX, None); let _ = NeuronCertificates::::clear_prefix(netuid, u32::MAX, None); @@ -602,15 +597,9 @@ impl Pallet { LastHotkeyEmissionOnNetuid::::remove(&hot, netuid); } } - // TotalHotkeyAlpha / TotalHotkeyAlphaLastEpoch / TotalHotkeyShares: (hot, netuid) → ... + // TotalHotkeyAlphaLastEpoch: (hot, netuid) → ... + // (TotalHotkeyAlpha and TotalHotkeyShares were already removed during dissolve.) { - let to_rm_alpha: sp_std::vec::Vec = TotalHotkeyAlpha::::iter() - .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) - .collect(); - for hot in to_rm_alpha { - TotalHotkeyAlpha::::remove(&hot, netuid); - } - let to_rm_alpha_last: sp_std::vec::Vec = TotalHotkeyAlphaLastEpoch::::iter() .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) @@ -618,24 +607,6 @@ impl Pallet { for hot in to_rm_alpha_last { TotalHotkeyAlphaLastEpoch::::remove(&hot, netuid); } - - let to_rm_shares: sp_std::vec::Vec = TotalHotkeyShares::::iter() - .filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None }) - .collect(); - for hot in to_rm_shares { - TotalHotkeyShares::::remove(&hot, netuid); - } - } - // Alpha shares NMAP: (hot, cold, netuid) → U64F64 - { - let to_rm: sp_std::vec::Vec<(T::AccountId, T::AccountId)> = Alpha::::iter() - .filter_map( - |((hot, cold, n), _)| if n == netuid { Some((hot, cold)) } else { None }, - ) - .collect(); - for (hot, cold) in to_rm { - Alpha::::remove((hot, cold, netuid)); - } } // TransactionKeyLastBlock NMAP: (hot, netuid, name) → u64 { @@ -674,7 +645,6 @@ impl Pallet { log::debug!( "remove_network: netuid={netuid}, owner={owner_coldkey:?} removed successfully" ); - Self::deposit_event(Event::NetworkRemoved(netuid)); } #[allow(clippy::arithmetic_side_effects)] From 576720ec16d4edfc08301c9112fbba037e834d30 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:45:06 -0700 Subject: [PATCH 70/97] remove duplicate check --- pallets/subtensor/src/coinbase/root.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 089b02d910..8f6d3c3530 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -758,9 +758,6 @@ impl Pallet { if !added || netuid == NetUid::ROOT { continue; } - if !Self::if_subnet_exist(netuid) { - continue; - } let registered_at = NetworkRegisteredAt::::get(netuid); From f4f5add76e8aaa04b8bccf08bfcf20ff78f8f22d Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:31:30 -0700 Subject: [PATCH 71/97] improve efficiency --- pallets/swap/src/pallet/impls.rs | 18 ++++++------------ pallets/swap/src/pallet/tests.rs | 4 ++-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index fb86c69e2b..16b9a58c53 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1362,23 +1362,22 @@ impl Pallet { let mechid = T::SubnetInfo::mechanism(netuid.into()); let v3_initialized = SwapV3Initialized::::get(netuid); let user_lp_enabled = - >::is_user_liquidity_enabled(netuid); + >::is_user_liquidity_enabled(netuid); let is_v3_mode = mechid == 1 && v3_initialized; if is_v3_mode { // -------- V3: close every position, aggregate refunds, clear state -------- - // 1) Snapshot all (owner, position_id) under this netuid to avoid iterator aliasing. + // 1) Snapshot all (owner, position_id). struct CloseItem { owner: A, pos_id: PositionId, } let mut to_close: sp_std::vec::Vec> = sp_std::vec::Vec::new(); - for ((n, owner, pos_id), _pos) in Positions::::iter() { - if n == netuid { - to_close.push(CloseItem { owner, pos_id }); - } + + for ((owner, pos_id), _pos) in Positions::::iter_prefix((netuid,)) { + to_close.push(CloseItem { owner, pos_id }); } let protocol_account = Self::protocol_account_id(); @@ -1428,14 +1427,10 @@ impl Pallet { ActiveTickIndexManager::::remove(netuid, ti); } - // 5) Clear storage: - // Positions (StorageNMap) – prefix is **(netuid,)** not just netuid. + // 5) Clear storage for this netuid. let _ = Positions::::clear_prefix((netuid,), u32::MAX, None); - - // Ticks (DoubleMap) – OK to pass netuid as first key. let _ = Ticks::::clear_prefix(netuid, u32::MAX, None); - // Fee globals, price/tick/liquidity, v3 init flag. FeeGlobalTao::::remove(netuid); FeeGlobalAlpha::::remove(netuid); CurrentLiquidity::::remove(netuid); @@ -1443,7 +1438,6 @@ impl Pallet { AlphaSqrtPrice::::remove(netuid); SwapV3Initialized::::remove(netuid); - // Active tick bitmap words (StorageNMap) – prefix is **(netuid,)**. let _ = TickIndexBitmapWords::::clear_prefix((netuid,), u32::MAX, None); FeeRate::::remove(netuid); EnabledUserLiquidity::::remove(netuid); diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index 863438c627..25ffbc1189 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -2295,8 +2295,8 @@ fn refund_alpha_single_provider_exact() { let (_pos_id, tao_needed, alpha_needed) = Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) .expect("add alpha-only liquidity"); - assert_eq!(tao_needed, 0, "alpha‑only position must not require TAO"); - assert!(alpha_needed > 0, "alpha‑only position must require ALPHA"); + assert_eq!(tao_needed, 0, "alpha-only position must not require TAO"); + assert!(alpha_needed > 0, "alpha-only position must require ALPHA"); // --- Snapshot BEFORE we withdraw funds (baseline for conservation). let alpha_before_hot = From 890f5b90177471614669dfe3b6d6632bc1d4e426 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 25 Aug 2025 15:46:17 -0700 Subject: [PATCH 72/97] migrate_subnet_limit_to_default --- pallets/subtensor/src/macros/hooks.rs | 4 +- .../migrate_subnet_limit_to_default.rs | 47 ++++++++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/tests/migration.rs | 49 +++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/migrations/migrate_subnet_limit_to_default.rs diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index cff92c17dd..d5037a2c60 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -135,7 +135,9 @@ mod hooks { // Migrate to fix root counters .saturating_add(migrations::migrate_fix_root_tao_and_alpha_in::migrate_fix_root_tao_and_alpha_in::()) // Migrate Immunity Period - .saturating_add(migrations::migrate_network_immunity_period::migrate_network_immunity_period::()); + .saturating_add(migrations::migrate_network_immunity_period::migrate_network_immunity_period::()) + // Migrate Subnet Limit + .saturating_add(migrations::migrate_subnet_limit_to_default::migrate_subnet_limit_to_default::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_subnet_limit_to_default.rs b/pallets/subtensor/src/migrations/migrate_subnet_limit_to_default.rs new file mode 100644 index 0000000000..d557589c88 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_subnet_limit_to_default.rs @@ -0,0 +1,47 @@ +use super::*; +use frame_support::{traits::Get, weights::Weight}; +use log; +use scale_info::prelude::string::String; + +pub fn migrate_subnet_limit_to_default() -> Weight { + let mig_name: Vec = b"subnet_limit_to_default".to_vec(); + + // 1 read: HasMigrationRun flag + let mut total_weight = T::DbWeight::get().reads(1); + + // Run once guard + if HasMigrationRun::::get(&mig_name) { + log::info!( + "Migration '{}' already executed - skipping", + String::from_utf8_lossy(&mig_name) + ); + return total_weight; + } + log::info!("Running migration '{}'", String::from_utf8_lossy(&mig_name)); + + // Read current and compute target default + let current: u16 = SubnetLimit::::get(); + let target: u16 = DefaultSubnetLimit::::get(); + + if current != target { + total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + SubnetLimit::::put(target); + log::info!("SubnetLimit updated: {} -> {}", current, target); + } else { + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); + log::info!( + "SubnetLimit already equals default ({}), no update performed.", + target + ); + } + + // Mark as done + HasMigrationRun::::insert(&mig_name, true); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{}' completed", + String::from_utf8_lossy(&mig_name) + ); + total_weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index cc1b4dad9d..e0dd295b7e 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -37,6 +37,7 @@ pub mod migrate_set_registration_enable; pub mod migrate_set_subtoken_enabled; pub mod migrate_stake_threshold; pub mod migrate_subnet_identities_to_v3; +pub mod migrate_subnet_limit_to_default; pub mod migrate_subnet_symbols; pub mod migrate_subnet_volume; pub mod migrate_to_v1_separate_emission; diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index fbe8825a5f..532c9cb062 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -1252,3 +1252,52 @@ fn test_migrate_crv3_v2_to_timelocked() { assert_eq!(round2, round); }); } + +#[test] +fn test_migrate_subnet_limit_to_default() { + new_test_ext(1).execute_with(|| { + // ------------------------------ + // 0. Constants / helpers + // ------------------------------ + const MIG_NAME: &[u8] = b"subnet_limit_to_default"; + + // Compute a non-default value safely + let default: u16 = DefaultSubnetLimit::::get(); + let not_default: u16 = default.wrapping_add(1); + + // ------------------------------ + // 1. Pre-state: ensure a non-default value is stored + // ------------------------------ + SubnetLimit::::put(not_default); + assert_eq!( + SubnetLimit::::get(), + not_default, + "precondition failed: SubnetLimit should be non-default before migration" + ); + + assert!( + !HasMigrationRun::::get(MIG_NAME.to_vec()), + "migration flag should be false before run" + ); + + // ------------------------------ + // 2. Run migration + // ------------------------------ + let w = crate::migrations::migrate_subnet_limit_to_default::migrate_subnet_limit_to_default::(); + assert!(!w.is_zero(), "weight must be non-zero"); + + // ------------------------------ + // 3. Verify results + // ------------------------------ + assert!( + HasMigrationRun::::get(MIG_NAME.to_vec()), + "migration flag not set" + ); + + assert_eq!( + SubnetLimit::::get(), + default, + "SubnetLimit should be reset to the configured default" + ); + }); +} From 4052094b32a4fd07e21a36e1a9930f6ca3f684e3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 26 Aug 2025 00:45:14 +0000 Subject: [PATCH 73/97] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 4c891828a4..8e90cbed33 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -777,7 +777,7 @@ mod dispatches { /// - Attempting to set prometheus information withing the rate limit min. /// #[pallet::call_index(40)] - #[pallet::weight((Weight::from_parts(32_310_000, 0) + #[pallet::weight((Weight::from_parts(41_320_000, 0) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] pub fn serve_axon_tls( @@ -2156,7 +2156,7 @@ mod dispatches { /// Emits a `SymbolUpdated` event on success. #[pallet::call_index(112)] #[pallet::weight(( - Weight::from_parts(46_040_000, 0).saturating_add(T::DbWeight::get().reads_writes(4, 1)), + Weight::from_parts(26_930_000, 0).saturating_add(T::DbWeight::get().reads_writes(4, 1)), DispatchClass::Operational, Pays::Yes ))] From ded32d4fb8b224ce43476288957242a7d56a4854 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:48:06 -0700 Subject: [PATCH 74/97] clippy --- .../src/migrations/migrate_subnet_limit_to_default.rs | 7 ++----- pallets/swap/src/pallet/impls.rs | 7 ++++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/migrations/migrate_subnet_limit_to_default.rs b/pallets/subtensor/src/migrations/migrate_subnet_limit_to_default.rs index d557589c88..3d88337a24 100644 --- a/pallets/subtensor/src/migrations/migrate_subnet_limit_to_default.rs +++ b/pallets/subtensor/src/migrations/migrate_subnet_limit_to_default.rs @@ -26,13 +26,10 @@ pub fn migrate_subnet_limit_to_default() -> Weight { if current != target { total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); SubnetLimit::::put(target); - log::info!("SubnetLimit updated: {} -> {}", current, target); + log::info!("SubnetLimit updated: {current} -> {target}"); } else { total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); - log::info!( - "SubnetLimit already equals default ({}), no update performed.", - target - ); + log::info!("SubnetLimit already equals default ({target}), no update performed."); } // Mark as done diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 16b9a58c53..30a967cd69 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1270,9 +1270,10 @@ impl Pallet { }); let mut k = 0usize; while leftover > 0 && k < remainders.len() { - let idx = remainders[k].0; - if let Some((_, amt)) = shares.get_mut(idx) { - *amt = amt.saturating_add(1); + if let Some((idx, _)) = remainders.get(k) { + if let Some((_, amt)) = shares.get_mut(*idx) { + *amt = amt.saturating_add(1); + } } leftover = leftover.saturating_sub(1); k = k.saturating_add(1); From 66a07b429d404555a3ba7ce5bcdb907fdd854271 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:30:10 -0700 Subject: [PATCH 75/97] add get_subnet_to_prune rpc --- pallets/subtensor/rpc/src/lib.rs | 17 +++++++++++++++++ pallets/subtensor/runtime-api/src/lib.rs | 1 + runtime/src/lib.rs | 3 +++ 3 files changed, 21 insertions(+) diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index e3d5d8f1c1..2f59bb5cdd 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -83,6 +83,8 @@ pub trait SubtensorCustomApi { metagraph_index: Vec, at: Option, ) -> RpcResult>; + #[method(name = "subnetInfo_getSubnetToPrune")] + fn get_subnet_to_prune(&self, at: Option) -> RpcResult>; } pub struct SubtensorCustom { @@ -427,4 +429,19 @@ where } } } + + fn get_subnet_to_prune( + &self, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + match api.get_subnet_to_prune(at) { + Ok(result) => Ok(result), + Err(e) => { + Err(Error::RuntimeError(format!("Unable to get subnet to prune: {e:?}")).into()) + } + } + } } diff --git a/pallets/subtensor/runtime-api/src/lib.rs b/pallets/subtensor/runtime-api/src/lib.rs index 42d12eb686..1a9c5dc09e 100644 --- a/pallets/subtensor/runtime-api/src/lib.rs +++ b/pallets/subtensor/runtime-api/src/lib.rs @@ -43,6 +43,7 @@ sp_api::decl_runtime_apis! { fn get_dynamic_info(netuid: NetUid) -> Option>; fn get_subnet_state(netuid: NetUid) -> Option>; fn get_selective_metagraph(netuid: NetUid, metagraph_indexes: Vec) -> Option>; + fn get_subnet_to_prune() -> Option; } pub trait StakeInfoRuntimeApi { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e4f304feda..f90f16f0af 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2334,6 +2334,9 @@ impl_runtime_apis! { fn get_selective_metagraph(netuid: NetUid, metagraph_indexes: Vec) -> Option> { SubtensorModule::get_selective_metagraph(netuid, metagraph_indexes) } + fn get_subnet_to_prune() -> Option { + pallet_subtensor::Pallet::::get_network_to_prune() + } } From db6614ec07aaaeb3c35d422621f749000577eca7 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 28 Aug 2025 19:15:20 -0700 Subject: [PATCH 76/97] don't dissolve root --- pallets/subtensor/src/coinbase/root.rs | 4 ++-- pallets/subtensor/src/tests/networks.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 8f6d3c3530..aaa85522f5 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -367,7 +367,7 @@ impl Pallet { pub fn do_dissolve_network(netuid: NetUid) -> dispatch::DispatchResult { // 1. --- The network exists? ensure!( - Self::if_subnet_exist(netuid), + Self::if_subnet_exist(netuid) && netuid != NetUid::ROOT, Error::::SubNetworkDoesNotExist ); @@ -418,7 +418,7 @@ impl Pallet { for (uid_i, weights_i) in as frame_support::storage::IterableStorageDoubleMap< NetUid, u16, - sp_std::vec::Vec<(u16, u16)>, + Vec<(u16, u16)>, >>::iter_prefix(NetUid::ROOT) { let mut modified_weights = weights_i.clone(); diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 4c00266587..d62bc4fd2f 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -1114,7 +1114,7 @@ fn prune_selection_complex_state_exhaustive() { // Remove n5; now n6 (price=0) should be selected. // This validates robustness to holes / non-contiguous netuids. // --------------------------------------------------------------------- - SubtensorModule::remove_network(n5); + SubtensorModule::do_dissolve_network(n5).expect("Expected not to panic"); assert_eq!( SubtensorModule::get_network_to_prune(), Some(n6), From aa08d50c086785e9eae79f19bca957338375394f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 31 Aug 2025 19:39:25 +0000 Subject: [PATCH 77/97] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index b6e01acb4f..eb2706f416 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -120,9 +120,9 @@ mod dispatches { /// - On failure for each failed item in the batch. /// #[pallet::call_index(80)] - #[pallet::weight((Weight::from_parts(19_330_000, 0) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(0_u64)), DispatchClass::Normal, Pays::No))] + #[pallet::weight((Weight::from_parts(95_140_000, 0) + .saturating_add(T::DbWeight::get().reads(14_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)), DispatchClass::Normal, Pays::No))] pub fn batch_set_weights( origin: OriginFor, netuids: Vec>, @@ -777,7 +777,7 @@ mod dispatches { /// - Attempting to set prometheus information withing the rate limit min. /// #[pallet::call_index(40)] - #[pallet::weight((Weight::from_parts(41_320_000, 0) + #[pallet::weight((Weight::from_parts(31_440_000, 0) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] pub fn serve_axon_tls( From a0830a784bca2bb2cfa8dbfa6f9b237b72f27a16 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Fri, 5 Sep 2025 10:42:32 -0700 Subject: [PATCH 78/97] don't remove LastAdjustmentBlock --- pallets/subtensor/src/coinbase/root.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index aaa85522f5..66f751d9d7 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -513,7 +513,6 @@ impl Pallet { MaxBurn::::remove(netuid); MinDifficulty::::remove(netuid); MaxDifficulty::::remove(netuid); - LastAdjustmentBlock::::remove(netuid); RegistrationsThisBlock::::remove(netuid); EMAPriceHalvingBlocks::::remove(netuid); RAORecycledForRegistration::::remove(netuid); From cd565b2391ecec4caee8fce92405f5900bb49feb Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sat, 6 Sep 2025 13:52:39 -0700 Subject: [PATCH 79/97] try_initialize_v3 --- pallets/subtensor/src/subnets/subnet.rs | 25 ++++++++++++++----------- pallets/swap-interface/src/lib.rs | 1 + pallets/swap/src/pallet/impls.rs | 3 +++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 522e267f3c..3460499b89 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -1,6 +1,7 @@ use super::*; use sp_core::Get; use subtensor_runtime_common::{NetUid, TaoCurrency}; +use subtensor_swap_interface::SwapHandler; impl Pallet { /// Fetches the total count of subnets. @@ -101,23 +102,23 @@ impl Pallet { /// Facilitates user registration of a new subnetwork. /// /// ### Args - /// * **`origin`** – `T::RuntimeOrigin`  Must be **signed** by the coldkey. - /// * **`hotkey`** – `&T::AccountId`  First neuron of the new subnet. - /// * **`mechid`** – `u16`  Only the dynamic mechanism (`1`) is currently supported. + /// * **`origin`** – `T::RuntimeOrigin`  Must be **signed** by the coldkey. + /// * **`hotkey`** – `&T::AccountId`  First neuron of the new subnet. + /// * **`mechid`** – `u16`  Only the dynamic mechanism (`1`) is currently supported. /// * **`identity`** – `Option`  Optional metadata for the subnet. /// /// ### Events - /// * `NetworkAdded(netuid, mechid)` – always. - /// * `SubnetIdentitySet(netuid)` – when a custom identity is supplied. + /// * `NetworkAdded(netuid, mechid)` – always. + /// * `SubnetIdentitySet(netuid)` – when a custom identity is supplied. /// * `NetworkRemoved(netuid)` – when a subnet is pruned to make room. /// /// ### Errors - /// * `NonAssociatedColdKey` – `hotkey` already belongs to another coldkey. - /// * `MechanismDoesNotExist` – unsupported `mechid`. - /// * `NetworkTxRateLimitExceeded` – caller hit the register-network rate limit. - /// * `SubnetLimitReached` – limit hit **and** no eligible subnet to prune. - /// * `CannotAffordLockCost` – caller lacks the lock cost. - /// * `BalanceWithdrawalError` – failed to lock balance. + /// * `NonAssociatedColdKey` – `hotkey` already belongs to another coldkey. + /// * `MechanismDoesNotExist` – unsupported `mechid`. + /// * `NetworkTxRateLimitExceeded` – caller hit the register-network rate limit. + /// * `SubnetLimitReached` – limit hit **and** no eligible subnet to prune. + /// * `CannotAffordLockCost` – caller lacks the lock cost. + /// * `BalanceWithdrawalError` – failed to lock balance. /// * `InvalidIdentity` – supplied `identity` failed validation. /// pub fn do_register_network( @@ -255,6 +256,8 @@ impl Pallet { Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); } + T::SwapInterface::try_initialize_v3(netuid_to_register)?; + // --- 18. Emit the NetworkAdded event. log::info!("NetworkAdded( netuid:{netuid_to_register:?}, mechanism:{mechid:?} )"); Self::deposit_event(Event::NetworkAdded(netuid_to_register, mechid)); diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index 268893f6a1..e0319c04b2 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -35,6 +35,7 @@ pub trait SwapHandler { ); fn is_user_liquidity_enabled(netuid: NetUid) -> bool; fn dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult; + fn try_initialize_v3(netuid: NetUid) -> DispatchResult; } #[derive(Debug, PartialEq)] diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 4d191e71d9..7053119562 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1575,6 +1575,9 @@ impl SwapHandler for Pallet { fn dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { Self::do_dissolve_all_liquidity_providers(netuid) } + fn try_initialize_v3(netuid: NetUid) -> DispatchResult { + Self::maybe_initialize_v3(netuid).map_err(|e| e.into()) + } } #[derive(Debug, PartialEq)] From 1b33b6f2bcf56ca1883241aece1543028367b24e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 7 Sep 2025 18:12:40 -0700 Subject: [PATCH 80/97] toggle_user_liquidity --- pallets/subtensor/src/subnets/subnet.rs | 14 +------------- pallets/swap-interface/src/lib.rs | 2 +- pallets/swap/src/pallet/impls.rs | 10 +++------- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 3460499b89..be42a09dde 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -4,17 +4,6 @@ use subtensor_runtime_common::{NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; impl Pallet { - /// Fetches the total count of subnets. - /// - /// This function retrieves the total number of subnets present on the chain. - /// - /// # Returns: - /// * 'u16': The total number of subnets. - /// - pub fn get_num_subnets() -> u16 { - TotalNetworks::::get() - } - /// Returns true if the subnetwork exists. /// /// This function checks if a subnetwork with the given UID exists. @@ -256,8 +245,7 @@ impl Pallet { Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); } - T::SwapInterface::try_initialize_v3(netuid_to_register)?; - + T::SwapInterface::toggle_user_liquidity(netuid_to_register, true); // --- 18. Emit the NetworkAdded event. log::info!("NetworkAdded( netuid:{netuid_to_register:?}, mechanism:{mechid:?} )"); Self::deposit_event(Event::NetworkAdded(netuid_to_register, mechid)); diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index e0319c04b2..d247b28d35 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -35,7 +35,7 @@ pub trait SwapHandler { ); fn is_user_liquidity_enabled(netuid: NetUid) -> bool; fn dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult; - fn try_initialize_v3(netuid: NetUid) -> DispatchResult; + fn toggle_user_liquidity(netuid: NetUid, enabled: bool); } #[derive(Debug, PartialEq)] diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 7053119562..75f050632c 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1360,14 +1360,10 @@ impl Pallet { /// - **V2 / non‑V3 path**: /// * No per‑position records exist; still defensively clear the same V3 storages (safe no‑ops). pub fn do_dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { - let mechid = T::SubnetInfo::mechanism(netuid.into()); - let v3_initialized = SwapV3Initialized::::get(netuid); let user_lp_enabled = >::is_user_liquidity_enabled(netuid); - let is_v3_mode = mechid == 1 && v3_initialized; - - if is_v3_mode { + if SwapV3Initialized::::get(netuid) { // -------- V3: close every position, aggregate refunds, clear state -------- // 1) Snapshot all (owner, position_id). @@ -1575,8 +1571,8 @@ impl SwapHandler for Pallet { fn dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { Self::do_dissolve_all_liquidity_providers(netuid) } - fn try_initialize_v3(netuid: NetUid) -> DispatchResult { - Self::maybe_initialize_v3(netuid).map_err(|e| e.into()) + fn toggle_user_liquidity(netuid: NetUid, enabled: bool) { + EnabledUserLiquidity::::insert(netuid, enabled) } } From 982c0053bed97e592d54b8fbaa85acbff222d63c Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:17:35 -0700 Subject: [PATCH 81/97] 128 subnet limit --- pallets/subtensor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 230a8b85a5..c37311c3a8 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -870,7 +870,7 @@ pub mod pallet { #[pallet::type_value] /// Default value for subnet limit. pub fn DefaultSubnetLimit() -> u16 { - 148 + 128 } #[pallet::storage] From 0700740fe6a2ec6558b0c1f9b8d18edd1d6fcf04 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:28:03 -0700 Subject: [PATCH 82/97] purge commitments --- pallets/admin-utils/src/tests/mock.rs | 6 ++ pallets/commitments/src/lib.rs | 12 +++ pallets/commitments/src/tests.rs | 121 +++++++++++++++++++++- pallets/subtensor/src/coinbase/root.rs | 2 + pallets/subtensor/src/lib.rs | 5 + pallets/subtensor/src/macros/config.rs | 4 + pallets/subtensor/src/tests/mock.rs | 6 ++ pallets/transaction-fee/src/tests/mock.rs | 6 ++ runtime/src/lib.rs | 12 ++- 9 files changed, 171 insertions(+), 3 deletions(-) diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 1308672d6d..d1667abb36 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -223,6 +223,7 @@ impl pallet_subtensor::Config for Test { type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; type ProxyInterface = (); type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; + type CommitmentsInterface = CommitmentsI; } parameter_types! { @@ -349,6 +350,11 @@ impl PrivilegeCmp for OriginPrivilegeCmp { } } +pub struct CommitmentsI; +impl pallet_subtensor::CommitmentsInterface for CommitmentsI { + fn purge_netuid(netuid: NetUid) {} +} + pub struct GrandpaInterfaceImpl; impl crate::GrandpaInterface for GrandpaInterfaceImpl { fn schedule_change( diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index 34192b6fa2..9e1e3d40b3 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -537,4 +537,16 @@ impl Pallet { Ok(()) } + + pub fn purge_netuid(netuid: NetUid) { + let _ = CommitmentOf::::clear_prefix(netuid, u32::MAX, None); + let _ = LastCommitment::::clear_prefix(netuid, u32::MAX, None); + let _ = LastBondsReset::::clear_prefix(netuid, u32::MAX, None); + let _ = RevealedCommitments::::clear_prefix(netuid, u32::MAX, None); + let _ = UsedSpaceOf::::clear_prefix(netuid, u32::MAX, None); + + TimelockedIndex::::mutate(|index| { + index.retain(|(n, _)| *n != netuid); + }); + } } diff --git a/pallets/commitments/src/tests.rs b/pallets/commitments/src/tests.rs index 6866ebdeec..5f19070ea2 100644 --- a/pallets/commitments/src/tests.rs +++ b/pallets/commitments/src/tests.rs @@ -4,8 +4,9 @@ use subtensor_runtime_common::NetUid; #[cfg(test)] use crate::{ - BalanceOf, CommitmentInfo, CommitmentOf, Config, Data, Error, Event, MaxSpace, Pallet, - Registration, RevealedCommitments, TimelockedIndex, UsedSpaceOf, + BalanceOf, CommitmentInfo, CommitmentOf, Config, Data, Error, Event, LastBondsReset, + LastCommitment, MaxSpace, Pallet, Registration, RevealedCommitments, TimelockedIndex, + UsageTracker, UsedSpaceOf, mock::{ Balances, DRAND_QUICKNET_SIG_2000_HEX, DRAND_QUICKNET_SIG_HEX, RuntimeEvent, RuntimeOrigin, Test, TestMaxFields, insert_drand_pulse, new_test_ext, produce_ciphertext, @@ -2185,3 +2186,119 @@ fn mixed_timelocked_and_raw_fields_works() { ); }); } + +#[test] +fn purge_netuid_clears_only_that_netuid() { + new_test_ext().execute_with(|| { + // Setup + System::::set_block_number(1); + + let net_a = NetUid::from(42); + let net_b = NetUid::from(43); + let who_a1: u64 = 1001; + let who_a2: u64 = 1002; + let who_b: u64 = 2001; + + // Minimal commitment payload + let empty_fields: BoundedVec::MaxFields> = BoundedVec::default(); + let info_empty: CommitmentInfo<::MaxFields> = CommitmentInfo { + fields: empty_fields, + }; + let bn = System::::block_number(); + + // Seed NET A with two accounts across all tracked storages + let reg_a1 = Registration { + deposit: Default::default(), + block: bn, + info: info_empty.clone(), + }; + let reg_a2 = Registration { + deposit: Default::default(), + block: bn, + info: info_empty.clone(), + }; + CommitmentOf::::insert(net_a, who_a1, reg_a1); + CommitmentOf::::insert(net_a, who_a2, reg_a2); + LastCommitment::::insert(net_a, who_a1, bn); + LastCommitment::::insert(net_a, who_a2, bn); + LastBondsReset::::insert(net_a, who_a1, bn); + RevealedCommitments::::insert(net_a, who_a1, vec![(b"a".to_vec(), 7u64)]); + UsedSpaceOf::::insert( + net_a, + who_a1, + UsageTracker { + last_epoch: 1, + used_space: 123, + }, + ); + + // Seed NET B with one account that must remain intact + let reg_b = Registration { + deposit: Default::default(), + block: bn, + info: info_empty, + }; + CommitmentOf::::insert(net_b, who_b, reg_b); + LastCommitment::::insert(net_b, who_b, bn); + LastBondsReset::::insert(net_b, who_b, bn); + RevealedCommitments::::insert(net_b, who_b, vec![(b"b".to_vec(), 8u64)]); + UsedSpaceOf::::insert( + net_b, + who_b, + UsageTracker { + last_epoch: 9, + used_space: 999, + }, + ); + + // Timelocked index contains both nets + TimelockedIndex::::mutate(|idx| { + idx.insert((net_a, who_a1)); + idx.insert((net_a, who_a2)); + idx.insert((net_b, who_b)); + }); + + // Sanity pre-checks + assert!(CommitmentOf::::get(net_a, who_a1).is_some()); + assert!(CommitmentOf::::get(net_b, who_b).is_some()); + assert!(TimelockedIndex::::get().contains(&(net_a, who_a1))); + + // Act + Pallet::::purge_netuid(net_a); + + // NET A: everything cleared + assert_eq!(CommitmentOf::::iter_prefix(net_a).count(), 0); + assert!(CommitmentOf::::get(net_a, who_a1).is_none()); + assert!(CommitmentOf::::get(net_a, who_a2).is_none()); + + assert_eq!(LastCommitment::::iter_prefix(net_a).count(), 0); + assert!(LastCommitment::::get(net_a, who_a1).is_none()); + assert!(LastCommitment::::get(net_a, who_a2).is_none()); + + assert_eq!(LastBondsReset::::iter_prefix(net_a).count(), 0); + assert!(LastBondsReset::::get(net_a, who_a1).is_none()); + + assert_eq!(RevealedCommitments::::iter_prefix(net_a).count(), 0); + assert!(RevealedCommitments::::get(net_a, who_a1).is_none()); + + assert_eq!(UsedSpaceOf::::iter_prefix(net_a).count(), 0); + assert!(UsedSpaceOf::::get(net_a, who_a1).is_none()); + + let idx_after = TimelockedIndex::::get(); + assert!(!idx_after.contains(&(net_a, who_a1))); + assert!(!idx_after.contains(&(net_a, who_a2))); + + // NET B: untouched + assert!(CommitmentOf::::get(net_b, who_b).is_some()); + assert!(LastCommitment::::get(net_b, who_b).is_some()); + assert!(LastBondsReset::::get(net_b, who_b).is_some()); + assert!(RevealedCommitments::::get(net_b, who_b).is_some()); + assert!(UsedSpaceOf::::get(net_b, who_b).is_some()); + assert!(idx_after.contains(&(net_b, who_b))); + + // Idempotency + Pallet::::purge_netuid(net_a); + assert_eq!(CommitmentOf::::iter_prefix(net_a).count(), 0); + assert!(!TimelockedIndex::::get().contains(&(net_a, who_a1))); + }); +} diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 66f751d9d7..1a6026b3ec 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -16,6 +16,7 @@ // DEALINGS IN THE SOFTWARE. use super::*; +use crate::CommitmentsInterface; use frame_support::{dispatch::Pays, weights::Weight}; use safe_math::*; use sp_core::Get; @@ -374,6 +375,7 @@ impl Pallet { // 2. --- Perform the cleanup before removing the network. T::SwapInterface::dissolve_all_liquidity_providers(netuid)?; Self::destroy_alpha_in_out_stakes(netuid)?; + T::CommitmentsInterface::purge_netuid(netuid); // 3. --- Remove the network Self::remove_network(netuid); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c37311c3a8..345135a6dd 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2164,3 +2164,8 @@ impl ProxyInterface for () { Ok(()) } } + +/// Pallets that hold per-subnet commitments implement this to purge all state for `netuid`. +pub trait CommitmentsInterface { + fn purge_netuid(netuid: NetUid); +} diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 3479ad8101..5e63875a91 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -6,6 +6,7 @@ use frame_support::pallet_macros::pallet_section; #[pallet_section] mod config { + use crate::CommitmentsInterface; use subtensor_swap_interface::SwapHandler; /// Configure the pallet by specifying the parameters and types on which it depends. @@ -58,6 +59,9 @@ mod config { /// Interface to allow interacting with the proxy pallet. type ProxyInterface: crate::ProxyInterface; + /// Interface to clean commitments on network dissolution. + type CommitmentsInterface: CommitmentsInterface; + /// ================================= /// ==== Initial Value Constants ==== /// ================================= diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 5838d5974f..c278acae0f 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -454,6 +454,7 @@ impl crate::Config for Test { type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; type ProxyInterface = FakeProxier; type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; + type CommitmentsInterface = CommitmentsI; } // Swap-related parameter types @@ -485,6 +486,11 @@ impl PrivilegeCmp for OriginPrivilegeCmp { } } +pub struct CommitmentsI; +impl CommitmentsInterface for CommitmentsI { + fn purge_netuid(netuid: NetUid) {} +} + parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index c2f5caa432..916fdc591c 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -288,6 +288,7 @@ impl pallet_subtensor::Config for Test { type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; type ProxyInterface = (); type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; + type CommitmentsInterface = CommitmentsI; } parameter_types! { @@ -414,6 +415,11 @@ impl PrivilegeCmp for OriginPrivilegeCmp { } } +pub struct CommitmentsI; +impl pallet_subtensor::CommitmentsInterface for CommitmentsI { + fn purge_netuid(netuid: NetUid) {} +} + parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8581483b91..5178efd261 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -491,7 +491,9 @@ impl CanVote for CanVoteToTriumvirate { } } -use pallet_subtensor::{CollectiveInterface, MemberManagement, ProxyInterface}; +use pallet_subtensor::{ + CollectiveInterface, CommitmentsInterface, MemberManagement, ProxyInterface, +}; pub struct ManageSenateMembers; impl MemberManagement for ManageSenateMembers { fn add_member(account: &AccountId) -> DispatchResultWithPostInfo { @@ -915,6 +917,13 @@ impl ProxyInterface for Proxier { } } +pub struct CommitmentsI; +impl CommitmentsInterface for CommitmentsI { + fn purge_netuid(netuid: NetUid) { + pallet_commitments::Pallet::::purge_netuid(netuid); + } +} + parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; @@ -1239,6 +1248,7 @@ impl pallet_subtensor::Config for Runtime { type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; type ProxyInterface = Proxier; type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; + type CommitmentsInterface = CommitmentsI; } parameter_types! { From 79da970f04d2ff40c2d95dbbc103e102fa84f980 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:28:08 -0700 Subject: [PATCH 83/97] fix tests --- pallets/subtensor/src/tests/networks.rs | 2 -- pallets/subtensor/src/tests/subnet.rs | 5 ----- 2 files changed, 7 deletions(-) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index d62bc4fd2f..ea1c236149 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -386,7 +386,6 @@ fn dissolve_clears_all_per_subnet_storages() { MaxBurn::::insert(net, TaoCurrency::from(2)); MinDifficulty::::insert(net, 1u64); MaxDifficulty::::insert(net, 2u64); - LastAdjustmentBlock::::insert(net, 1u64); RegistrationsThisBlock::::insert(net, 1u16); EMAPriceHalvingBlocks::::insert(net, 1u64); RAORecycledForRegistration::::insert(net, TaoCurrency::from(1)); @@ -534,7 +533,6 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!MaxBurn::::contains_key(net)); assert!(!MinDifficulty::::contains_key(net)); assert!(!MaxDifficulty::::contains_key(net)); - assert!(!LastAdjustmentBlock::::contains_key(net)); assert!(!RegistrationsThisBlock::::contains_key(net)); assert!(!EMAPriceHalvingBlocks::::contains_key(net)); assert!(!RAORecycledForRegistration::::contains_key(net)); diff --git a/pallets/subtensor/src/tests/subnet.rs b/pallets/subtensor/src/tests/subnet.rs index 6bf4a6873b..ba5640af3d 100644 --- a/pallets/subtensor/src/tests/subnet.rs +++ b/pallets/subtensor/src/tests/subnet.rs @@ -727,11 +727,6 @@ fn test_user_liquidity_access_control() { // add network let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Initially should be disabled - assert!(!pallet_subtensor_swap::EnabledUserLiquidity::::get( - NetUid::from(netuid) - )); - // Not owner, not root: should fail assert_noop!( Swap::toggle_user_liquidity(RuntimeOrigin::signed(not_owner), netuid, true), From d4ac33fbaafb93829ecda1289140935b3fd8e386 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:48:36 -0700 Subject: [PATCH 84/97] cleanup merge --- pallets/admin-utils/src/tests/mock.rs | 2 +- pallets/commitments/src/lib.rs | 20 ++++++++++---------- pallets/subtensor/src/macros/config.rs | 2 +- pallets/subtensor/src/subnets/subnet.rs | 1 - pallets/subtensor/src/tests/mock.rs | 2 +- pallets/transaction-fee/src/tests/mock.rs | 2 +- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 19664c50f0..94c9c0be58 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -357,7 +357,7 @@ impl PrivilegeCmp for OriginPrivilegeCmp { pub struct CommitmentsI; impl pallet_subtensor::CommitmentsInterface for CommitmentsI { - fn purge_netuid(netuid: NetUid) {} + fn purge_netuid(_netuid: NetUid) {} } pub struct GrandpaInterfaceImpl; diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index b1a3e40403..5fa37bf5e1 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -566,16 +566,6 @@ impl Pallet { .collect(); commitments } -} - -pub trait GetCommitments { - fn get_commitments(netuid: NetUid) -> Vec<(AccountId, Vec)>; -} - -impl GetCommitments for () { - fn get_commitments(_netuid: NetUid) -> Vec<(AccountId, Vec)> { - Vec::new() - } pub fn purge_netuid(netuid: NetUid) { let _ = CommitmentOf::::clear_prefix(netuid, u32::MAX, None); @@ -589,3 +579,13 @@ impl GetCommitments for () { }); } } + +pub trait GetCommitments { + fn get_commitments(netuid: NetUid) -> Vec<(AccountId, Vec)>; +} + +impl GetCommitments for () { + fn get_commitments(_netuid: NetUid) -> Vec<(AccountId, Vec)> { + Vec::new() + } +} diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 9bc610c958..8d624348d0 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -6,8 +6,8 @@ use frame_support::pallet_macros::pallet_section; #[pallet_section] mod config { - use pallet_commitments::GetCommitments; use crate::CommitmentsInterface; + use pallet_commitments::GetCommitments; use subtensor_swap_interface::SwapHandler; /// Configure the pallet by specifying the parameters and types on which it depends. diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 0051e34b87..3ce40cc366 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -195,7 +195,6 @@ impl Pallet { log::debug!("SubnetMechanism for netuid {netuid_to_register:?} set to: {mechid:?}"); // --- 14. Set the creation terms. - NetworkLastRegistered::::set(current_block); NetworkRegisteredAt::::insert(netuid_to_register, current_block); // --- 15. Set the symbol. diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index f634268267..8607e14ec7 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -493,7 +493,7 @@ impl PrivilegeCmp for OriginPrivilegeCmp { pub struct CommitmentsI; impl CommitmentsInterface for CommitmentsI { - fn purge_netuid(netuid: NetUid) {} + fn purge_netuid(_netuid: NetUid) {} } parameter_types! { diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 51e2b62da9..532c140c9e 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -422,7 +422,7 @@ impl PrivilegeCmp for OriginPrivilegeCmp { pub struct CommitmentsI; impl pallet_subtensor::CommitmentsInterface for CommitmentsI { - fn purge_netuid(netuid: NetUid) {} + fn purge_netuid(_netuid: NetUid) {} } parameter_types! { From 0c5c596dfa7602fff5865a57c98e3020b642e5bf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 8 Sep 2025 22:21:54 +0000 Subject: [PATCH 85/97] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index b9f3391d5e..80459643d4 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -623,7 +623,7 @@ mod dispatches { /// - Attempting to set prometheus information withing the rate limit min. /// #[pallet::call_index(40)] - #[pallet::weight((Weight::from_parts(41_240_000, 0) + #[pallet::weight((Weight::from_parts(32_440_000, 0) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] pub fn serve_axon_tls( @@ -1041,7 +1041,7 @@ mod dispatches { #[pallet::call_index(59)] #[pallet::weight((Weight::from_parts(235_400_000, 0) .saturating_add(T::DbWeight::get().reads(37_u64)) - .saturating_add(T::DbWeight::get().writes(59_u64)), DispatchClass::Normal, Pays::No))] + .saturating_add(T::DbWeight::get().writes(60_u64)), DispatchClass::Normal, Pays::No))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) } @@ -1328,7 +1328,7 @@ mod dispatches { #[pallet::call_index(79)] #[pallet::weight((Weight::from_parts(234_200_000, 0) .saturating_add(T::DbWeight::get().reads(36_u64)) - .saturating_add(T::DbWeight::get().writes(58_u64)), DispatchClass::Normal, Pays::No))] + .saturating_add(T::DbWeight::get().writes(59_u64)), DispatchClass::Normal, Pays::No))] pub fn register_network_with_identity( origin: OriginFor, hotkey: T::AccountId, From a3acec860f4d3a1e9cee1b54bf1de0f3a083224c Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:40:08 -0700 Subject: [PATCH 86/97] improve logic --- pallets/subtensor/src/coinbase/root.rs | 2 +- pallets/subtensor/src/staking/remove_stake.rs | 33 ++- pallets/subtensor/src/subnets/subnet.rs | 1 - pallets/subtensor/src/tests/networks.rs | 24 ++- pallets/swap/src/pallet/impls.rs | 202 ++---------------- 5 files changed, 58 insertions(+), 204 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 7b4a4e447c..fa16e13189 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -381,7 +381,7 @@ impl Pallet { Self::remove_network(netuid); // 4. --- Emit the NetworkRemoved event - log::debug!("NetworkRemoved( netuid:{netuid:?} )"); + log::info!("NetworkRemoved( netuid:{netuid:?} )"); Self::deposit_event(Event::NetworkRemoved(netuid)); Ok(()) diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 1169f99406..d3834c283a 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -456,8 +456,7 @@ impl Pallet { // Emission:: is Vec. We: // - sum emitted α, // - apply owner fraction to get owner α, - // - convert owner α to τ using current price, - // - use that τ value for the refund formula. + // - price that α using a *simulated* AMM swap. let total_emitted_alpha_u128: u128 = Emission::::get(netuid) .into_iter() @@ -472,15 +471,27 @@ impl Pallet { .floor() .saturating_to_num::(); - // Current α→τ price (TAO per 1 α) for this subnet. - let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); - - // Convert owner α to τ at current price; floor to integer τ. - let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) - .saturating_mul(cur_price) - .floor() - .saturating_to_num::(); - let owner_emission_tao: TaoCurrency = owner_emission_tao_u64.into(); + let owner_emission_tao: TaoCurrency = if owner_alpha_u64 > 0 { + match T::SwapInterface::sim_swap(netuid.into(), OrderType::Sell, owner_alpha_u64) { + Ok(sim) => TaoCurrency::from(sim.amount_paid_out), + Err(e) => { + log::debug!( + "destroy_alpha_in_out_stakes: sim_swap owner α→τ failed (netuid={:?}, alpha={}, err={:?}); falling back to price multiply.", + netuid, + owner_alpha_u64, + e + ); + let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); + let val_u64: u64 = U96F32::from_num(owner_alpha_u64) + .saturating_mul(cur_price) + .floor() + .saturating_to_num::(); + TaoCurrency::from(val_u64) + } + } + } else { + TaoCurrency::ZERO + }; // 4) Enumerate all α entries on this subnet to build distribution weights and cleanup lists. // - collect keys to remove, diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 3ce40cc366..abaa1e02c5 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -213,7 +213,6 @@ impl Pallet { SubnetAlphaIn::::insert(netuid_to_register, pool_initial_alpha); SubnetOwner::::insert(netuid_to_register, coldkey.clone()); SubnetOwnerHotkey::::insert(netuid_to_register, hotkey.clone()); - TransferToggle::::insert(netuid_to_register, true); SubnetLocked::::insert(netuid_to_register, pool_initial_tao); LargestLocked::::insert(netuid_to_register, pool_initial_tao.to_u64()); SubnetTaoProvided::::insert(netuid_to_register, TaoCurrency::ZERO); diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index ea1c236149..b70001c2b0 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -7,7 +7,7 @@ use sp_core::U256; use sp_std::collections::btree_map::BTreeMap; use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::TaoCurrency; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::{OrderType, SwapHandler}; #[test] fn test_registration_ok() { @@ -859,12 +859,22 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { .floor() .saturating_to_num::(); - let price: U96F32 = - ::SwapInterface::current_alpha_price(netuid.into()); - let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) - .saturating_mul(price) - .floor() - .saturating_to_num::(); + let owner_emission_tao_u64: u64 = ::SwapInterface::sim_swap( + netuid.into(), + OrderType::Sell, + owner_alpha_u64, + ) + .map(|res| res.amount_paid_out) + .unwrap_or_else(|_| { + // Fallback matches the pallet's fallback + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); + U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::() + }); + let expected_refund: u64 = lock.saturating_sub(owner_emission_tao_u64); // ── 6) run distribution (credits τ to coldkeys, wipes α state) ───── diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 75f050632c..c3652b10c9 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1213,218 +1213,55 @@ impl Pallet { T::ProtocolId::get().into_account_truncating() } - /// Distribute `alpha_total` back to the coldkey's hotkeys for `netuid`. - /// - Pro‑rata by current α stake on this subnet; if all zero, split evenly. - /// - Deterministic "largest remainders" rounding to ensure exact conservation. - /// - Robust to partial deposit failures: retries across successes, final fallback to (cold, cold). - pub fn refund_alpha(netuid: NetUid, coldkey: &T::AccountId, alpha_total: AlphaCurrency) { - if alpha_total.is_zero() { - return; - } - - // 1) Recipient set - let mut hotkeys: sp_std::vec::Vec = T::SubnetInfo::get_owned_hotkeys(coldkey); - if hotkeys.is_empty() { - hotkeys.push(coldkey.clone()); - } - - // 2) Weights = current α stake per hotkey; if all zero -> even split - let weights: sp_std::vec::Vec = hotkeys - .iter() - .map(|hk| u128::from(T::BalanceOps::alpha_balance(netuid, coldkey, hk).to_u64())) - .collect(); - - let sum_weights: u128 = weights - .iter() - .copied() - .fold(0u128, |acc, w| acc.saturating_add(w)); - let total_u128: u128 = u128::from(alpha_total.to_u64()); - let n = hotkeys.len(); - - // (account, planned_amount_u64) - let mut shares: sp_std::vec::Vec<(T::AccountId, u64)> = sp_std::vec::Vec::with_capacity(n); - - if sum_weights > 0 { - // 3a) Pro‑rata base + largest remainders (deterministic) - let mut bases: sp_std::vec::Vec = sp_std::vec::Vec::with_capacity(n); - let mut remainders: sp_std::vec::Vec<(usize, u128)> = - sp_std::vec::Vec::with_capacity(n); - - let mut base_sum: u128 = 0; - for (i, (&w, hk)) in weights.iter().zip(hotkeys.iter()).enumerate() { - let numer = total_u128.saturating_mul(w); - let base = numer.checked_div(sum_weights).unwrap_or(0); - let rem = numer.checked_rem(sum_weights).unwrap_or(0); - bases.push(base); - remainders.push((i, rem)); - base_sum = base_sum.saturating_add(base); - shares.push((hk.clone(), u64::try_from(base).unwrap_or(u64::MAX))); - } - - // Distribute leftover ones to the largest remainders; tie‑break by index for determinism - let mut leftover = total_u128.saturating_sub(base_sum); - if leftover > 0 { - remainders.sort_by(|a, b| { - // Descending by remainder, then ascending by index - b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)) - }); - let mut k = 0usize; - while leftover > 0 && k < remainders.len() { - if let Some((idx, _)) = remainders.get(k) { - if let Some((_, amt)) = shares.get_mut(*idx) { - *amt = amt.saturating_add(1); - } - } - leftover = leftover.saturating_sub(1); - k = k.saturating_add(1); - } - } - } else { - // 3b) Even split with deterministic round‑robin remainder - let base = total_u128.checked_div(n as u128).unwrap_or(0); - let mut rem = total_u128.checked_rem(n as u128).unwrap_or(0); - for hk in hotkeys.iter() { - let mut amt = u64::try_from(base).unwrap_or(u64::MAX); - if rem > 0 { - amt = amt.saturating_add(1); - rem = rem.saturating_sub(1); - } - shares.push((hk.clone(), amt)); - } - } - - // 4) Deposit to (coldkey, each hotkey). Track leftover if any deposit fails. - let mut leftover: u64 = 0; - let mut successes: sp_std::vec::Vec = sp_std::vec::Vec::new(); - - for (hk, amt_u64) in shares.iter() { - if *amt_u64 == 0 { - continue; - } - let amt: AlphaCurrency = (*amt_u64).into(); - match T::BalanceOps::increase_stake(coldkey, hk, netuid, amt) { - Ok(_) => successes.push(hk.clone()), - Err(e) => { - log::warn!( - "refund_alpha: increase_stake failed (cold={coldkey:?}, hot={hk:?}, netuid={netuid:?}, amt={amt_u64:?}): {e:?}" - ); - leftover = leftover.saturating_add(*amt_u64); - } - } - } - - // 5) Retry: spread any leftover across the hotkeys that succeeded in step 4. - if leftover > 0 && !successes.is_empty() { - let count = successes.len() as u64; - let base = leftover.checked_div(count).unwrap_or(0); - let mut rem = leftover.checked_rem(count).unwrap_or(0); - - let mut leftover_retry: u64 = 0; - for hk in successes.iter() { - let add: u64 = base.saturating_add(if rem > 0 { - rem = rem.saturating_sub(1); - 1 - } else { - 0 - }); - if add == 0 { - continue; - } - if let Err(e) = T::BalanceOps::increase_stake(coldkey, hk, netuid, add.into()) { - log::warn!( - "refund_alpha(retry): increase_stake failed (cold={coldkey:?}, hot={hk:?}, netuid={netuid:?}, amt={add:?}): {e:?}" - ); - leftover_retry = leftover_retry.saturating_add(add); - } - } - leftover = leftover_retry; - } - - // 6) Final fallback: deposit any remainder to (coldkey, coldkey). - if leftover > 0 { - let _ = T::BalanceOps::increase_stake(coldkey, coldkey, netuid, leftover.into()); - } - } - - /// Dissolve all LPs for `netuid`, refund providers, and reset all swap state. - /// - /// - **V3 path** (mechanism == 1 && SwapV3Initialized): - /// * Remove **all** positions via `do_remove_liquidity`. - /// * **Refund** each owner: - /// - TAO = Σ(position.tao + position.fee_tao) → credited to the owner's **coldkey** free balance. - /// - ALPHA = Σ(position.alpha + position.fee_alpha) → credited back via `refund_alpha`. - /// * Decrease "provided reserves" (principal only) for non‑protocol owners. - /// * Clear ActiveTickIndexManager entries, ticks, fee globals, price, tick, liquidity, - /// init flag, bitmap words, fee rate knob, and user LP flag. - /// - /// - **V2 / non‑V3 path**: - /// * No per‑position records exist; still defensively clear the same V3 storages (safe no‑ops). + /// Dissolve all LPs and clean state. pub fn do_dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { let user_lp_enabled = >::is_user_liquidity_enabled(netuid); if SwapV3Initialized::::get(netuid) { - // -------- V3: close every position, aggregate refunds, clear state -------- - - // 1) Snapshot all (owner, position_id). + // 1) Snapshot (owner, position_id). struct CloseItem { owner: A, pos_id: PositionId, } let mut to_close: sp_std::vec::Vec> = sp_std::vec::Vec::new(); - for ((owner, pos_id), _pos) in Positions::::iter_prefix((netuid,)) { to_close.push(CloseItem { owner, pos_id }); } let protocol_account = Self::protocol_account_id(); - // 2) Aggregate refunds per owner while removing positions. - use sp_std::collections::btree_map::BTreeMap; - let mut refunds: BTreeMap = BTreeMap::new(); + // Non‑protocol first + to_close + .sort_by(|a, b| (a.owner == protocol_account).cmp(&(b.owner == protocol_account))); for CloseItem { owner, pos_id } in to_close.into_iter() { let rm = Self::do_remove_liquidity(netuid, &owner, pos_id)?; - // Accumulate (TAO, α) refund: principal + fees. - let tao_add = rm.tao.saturating_add(rm.fee_tao); - let alpha_add = rm.alpha.saturating_add(rm.fee_alpha); - - refunds - .entry(owner.clone()) - .and_modify(|(t, a)| { - *t = t.saturating_add(tao_add); - *a = a.saturating_add(alpha_add); - }) - .or_insert((tao_add, alpha_add)); + // τ: refund **principal only** (no τ fees). + if rm.tao > TaoCurrency::ZERO { + T::BalanceOps::increase_balance(&owner, rm.tao); + } if owner != protocol_account { + // Principal reserves decrease T::BalanceOps::decrease_provided_tao_reserve(netuid, rm.tao); - T::BalanceOps::decrease_provided_alpha_reserve(netuid, rm.alpha); - } - } - // 3) Process refunds per owner. - for (owner, (tao_sum, alpha_sum)) in refunds.into_iter() { - // TAO → coldkey free balance - if tao_sum > TaoCurrency::ZERO { - T::BalanceOps::increase_balance(&owner, tao_sum); - } - - // α → split across all hotkeys owned by `owner`. - if !alpha_sum.is_zero() && owner != protocol_account { - Self::refund_alpha(netuid, &owner, alpha_sum); + // Burn α (principal + fees) from provided reserves; do not credit to users. + let alpha_burn = rm.alpha.saturating_add(rm.fee_alpha); + if alpha_burn > AlphaCurrency::ZERO { + T::BalanceOps::decrease_provided_alpha_reserve(netuid, alpha_burn); + } } } - // 4) Clear active tick index set by walking ticks we are about to clear. + // 3) Clear active tick index entries, then all swap state. let active_ticks: sp_std::vec::Vec = Ticks::::iter_prefix(netuid).map(|(ti, _)| ti).collect(); for ti in active_ticks { ActiveTickIndexManager::::remove(netuid, ti); } - // 5) Clear storage for this netuid. let _ = Positions::::clear_prefix((netuid,), u32::MAX, None); let _ = Ticks::::clear_prefix(netuid, u32::MAX, None); @@ -1440,22 +1277,19 @@ impl Pallet { EnabledUserLiquidity::::remove(netuid); log::debug!( - "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V3, user_lp_enabled={user_lp_enabled}, v3_state_cleared + refunds" + "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V3, user_lp_enabled={user_lp_enabled}, positions closed; τ principal refunded; α burned; state cleared" ); return Ok(()); } - // -------- V2 / non‑V3: no positions to close; still nuke any V3 residues -------- - + // V2 / non‑V3: ensure V3 residues are cleared (safe no‑ops). let _ = Positions::::clear_prefix((netuid,), u32::MAX, None); - let active_ticks: sp_std::vec::Vec = Ticks::::iter_prefix(netuid).map(|(ti, _)| ti).collect(); for ti in active_ticks { ActiveTickIndexManager::::remove(netuid, ti); } - let _ = Ticks::::clear_prefix(netuid, u32::MAX, None); FeeGlobalTao::::remove(netuid); From b654eafa69f4fbbc24c485efd48d9dba10fb2825 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:36:34 -0700 Subject: [PATCH 87/97] add migrate_network_lock_reduction_interval --- pallets/subtensor/src/coinbase/root.rs | 1 + pallets/subtensor/src/macros/hooks.rs | 4 +- .../migrations/migrate_init_total_issuance.rs | 2 +- ...migrate_network_lock_reduction_interval.rs | 47 +++++++++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/subnets/subnet.rs | 7 ++- pallets/subtensor/src/tests/migration.rs | 30 ++++++++++++ 7 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index fa16e13189..113bd5b1af 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -492,6 +492,7 @@ impl Pallet { PendingOwnerCut::::remove(netuid); BlocksSinceLastStep::::remove(netuid); LastMechansimStepBlock::::remove(netuid); + LastAdjustmentBlock::::remove(netuid); // --- 16. Serving / rho / curves, and other per-net controls. ServingRateLimit::::remove(netuid); diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 1615e87cd6..12ef167ef9 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -141,7 +141,9 @@ mod hooks { // Migrate Immunity Period .saturating_add(migrations::migrate_network_immunity_period::migrate_network_immunity_period::()) // Migrate Subnet Limit - .saturating_add(migrations::migrate_subnet_limit_to_default::migrate_subnet_limit_to_default::()); + .saturating_add(migrations::migrate_subnet_limit_to_default::migrate_subnet_limit_to_default::()) + // Migrate Lock Reduction Interval + .saturating_add(migrations::migrate_network_lock_reduction_interval::migrate_network_lock_reduction_interval::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_init_total_issuance.rs b/pallets/subtensor/src/migrations/migrate_init_total_issuance.rs index 042ad0fe77..6a05dc5a85 100644 --- a/pallets/subtensor/src/migrations/migrate_init_total_issuance.rs +++ b/pallets/subtensor/src/migrations/migrate_init_total_issuance.rs @@ -15,7 +15,7 @@ pub mod deprecated_loaded_emission_format { } pub(crate) fn migrate_init_total_issuance() -> Weight { - let subnets_len = crate::SubnetLocked::::iter().count() as u64; + let subnets_len = crate::NetworksAdded::::iter().count() as u64; // Retrieve the total balance of all accounts let total_account_balances = <::Currency as fungible::Inspect< diff --git a/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs b/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs new file mode 100644 index 0000000000..56079b0a13 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs @@ -0,0 +1,47 @@ +use super::*; +use frame_support::{traits::Get, weights::Weight}; +use log; +use scale_info::prelude::string::String; + +pub fn migrate_network_lock_reduction_interval() -> Weight { + const NEW_VALUE: u64 = 28_800; + + let migration_name = b"migrate_network_lock_reduction_interval".to_vec(); + let mut weight = T::DbWeight::get().reads(1); + + // Skip if already executed + if HasMigrationRun::::get(&migration_name) { + log::info!( + target: "runtime", + "Migration '{}' already run - skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + // ── 1) Set new values ───────────────────────────────────────────────── + NetworkLockReductionInterval::::put(NEW_VALUE); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + NetworkRateLimit::::put(NEW_VALUE); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + Pallet::::set_network_last_lock(TaoCurrency::from(1_000_000_000_000)); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + Pallet::::set_network_last_lock_block(Pallet::::get_current_block_as_u64()); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // ── 2) Mark migration done ─────────────────────────────────────────── + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + target: "runtime", + "Migration '{}' completed - NetworkLockReductionInterval => {}.", + String::from_utf8_lossy(&migration_name), + NEW_VALUE + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index a12f04627b..55b249decd 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -20,6 +20,7 @@ pub mod migrate_fix_root_tao_and_alpha_in; pub mod migrate_identities_v2; pub mod migrate_init_total_issuance; pub mod migrate_network_immunity_period; +pub mod migrate_network_lock_reduction_interval; pub mod migrate_orphaned_storage_items; pub mod migrate_populate_owned_hotkeys; pub mod migrate_rao; diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index abaa1e02c5..42f2ff92b5 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -213,11 +213,10 @@ impl Pallet { SubnetAlphaIn::::insert(netuid_to_register, pool_initial_alpha); SubnetOwner::::insert(netuid_to_register, coldkey.clone()); SubnetOwnerHotkey::::insert(netuid_to_register, hotkey.clone()); - SubnetLocked::::insert(netuid_to_register, pool_initial_tao); - LargestLocked::::insert(netuid_to_register, pool_initial_tao.to_u64()); + SubnetLocked::::insert(netuid_to_register, actual_tao_lock_amount); SubnetTaoProvided::::insert(netuid_to_register, TaoCurrency::ZERO); - SubnetAlphaInProvided::::insert(netuid_to_register, AlphaCurrency::from(0)); - SubnetAlphaOut::::insert(netuid_to_register, AlphaCurrency::from(0)); + SubnetAlphaInProvided::::insert(netuid_to_register, AlphaCurrency::ZERO); + SubnetAlphaOut::::insert(netuid_to_register, AlphaCurrency::ZERO); SubnetVolume::::insert(netuid_to_register, 0u128); RAORecycledForRegistration::::insert( netuid_to_register, diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index f860248cce..f953e8eb47 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -1624,3 +1624,33 @@ fn test_migrate_subnet_limit_to_default() { ); }); } + +#[test] +fn test_migrate_network_lock_reduction_interval_and_decay() { + new_test_ext(0).execute_with(|| { + // ── pre ────────────────────────────────────────────────────────────── + assert!( + !HasMigrationRun::::get(b"migrate_network_lock_reduction_interval".to_vec()), + "HasMigrationRun should be false before migration" + ); + + // ensure current_block > 0 so mult = 2 after migration + step_block(1); + + // ── run migration ──────────────────────────────────────────────────── + let weight = crate::migrations::migrate_network_lock_reduction_interval::migrate_network_lock_reduction_interval::(); + assert!(!weight.is_zero(), "migration weight should be > 0"); + + // ── params & flags ─────────────────────────────────────────────────── + assert_eq!(NetworkLockReductionInterval::::get(), 28_800); + assert_eq!(NetworkRateLimit::::get(), 28_800); + assert_eq!(Pallet::::get_network_last_lock(), 1_000_000_000_000u64.into()); // 1000 TAO in rAO + + let start_block = Pallet::::get_network_last_lock_block(); + assert_eq!(start_block, Pallet::::get_current_block_as_u64()); + assert!( + HasMigrationRun::::get(b"migrate_network_lock_reduction_interval".to_vec()), + "HasMigrationRun should be true after migration" + ); + }); +} From d9fb4d22c15c00bbb72f3ec4e61fc31c3d4af5ce Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 15 Sep 2025 07:36:08 -0700 Subject: [PATCH 88/97] fix merge --- pallets/subtensor/src/coinbase/root.rs | 36 ++++++++-------------- pallets/subtensor/src/macros/dispatches.rs | 2 +- pallets/subtensor/src/tests/networks.rs | 32 ++++++++++++------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 4702eb3e85..03a82cac93 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -391,7 +391,6 @@ impl Pallet { // --- 1. Get the owner and remove from SubnetOwner. let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); SubnetOwner::::remove(netuid); - let subsubnets: u8 = SubsubnetCountCurrent::::get(netuid).into(); // --- 2. Remove network count. SubnetworkN::::remove(netuid); @@ -409,16 +408,6 @@ impl Pallet { let _ = Uids::::clear_prefix(netuid, u32::MAX, None); let keys = Keys::::iter_prefix(netuid).collect::>(); let _ = Keys::::clear_prefix(netuid, u32::MAX, None); - for subid in 0..subsubnets { - let netuid_index = Self::get_subsubnet_storage_index(netuid, subid.into()); - let _ = Bonds::::clear_prefix(netuid_index, u32::MAX, None); - } - - // --- 7. Remove the weights for this subnet itself. - for subid in 0..subsubnets { - let netuid_index = Self::get_subsubnet_storage_index(netuid, subid.into()); - let _ = Weights::::clear_prefix(netuid_index, u32::MAX, None); - } // --- 8. Iterate over stored weights and fill the matrix. for (uid_i, weights_i) in Weights::::iter_prefix(NetUidStorageIndex::ROOT) { @@ -438,17 +427,10 @@ impl Pallet { Trust::::remove(netuid); Active::::remove(netuid); Emission::::remove(netuid); - for subid in 0..subsubnets { - let netuid_index = Self::get_subsubnet_storage_index(netuid, subid.into()); - Incentive::::remove(netuid_index); - } + Consensus::::remove(netuid); Dividends::::remove(netuid); PruningScores::::remove(netuid); - for subid in 0..subsubnets { - let netuid_index = Self::get_subsubnet_storage_index(netuid, subid.into()); - LastUpdate::::remove(netuid_index); - } ValidatorPermit::::remove(netuid); ValidatorTrust::::remove(netuid); @@ -550,10 +532,18 @@ impl Pallet { let _ = AssociatedEvmAddress::::clear_prefix(netuid, u32::MAX, None); // Commit-reveal / weights commits (all per-net prefixes): - let _ = WeightCommits::::clear_prefix(netuid, u32::MAX, None); - let _ = TimelockedWeightCommits::::clear_prefix(netuid, u32::MAX, None); - let _ = CRV3WeightCommits::::clear_prefix(netuid, u32::MAX, None); - let _ = CRV3WeightCommitsV2::::clear_prefix(netuid, u32::MAX, None); + let subsubnets: u8 = SubsubnetCountCurrent::::get(netuid).into(); + for subid in 0..subsubnets { + let netuid_index = Self::get_subsubnet_storage_index(netuid, subid.into()); + LastUpdate::::remove(netuid_index); + Incentive::::remove(netuid_index); + let _ = WeightCommits::::clear_prefix(netuid_index, u32::MAX, None); + let _ = TimelockedWeightCommits::::clear_prefix(netuid_index, u32::MAX, None); + let _ = CRV3WeightCommits::::clear_prefix(netuid_index, u32::MAX, None); + let _ = CRV3WeightCommitsV2::::clear_prefix(netuid_index, u32::MAX, None); + let _ = Bonds::::clear_prefix(netuid_index, u32::MAX, None); + let _ = Weights::::clear_prefix(netuid_index, u32::MAX, None); + } RevealPeriodEpochs::::remove(netuid); // Last hotkey swap (DMAP where netuid is FIRST key → easy) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 3544ed9f92..3d89eacb5f 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2346,7 +2346,7 @@ mod dispatches { /// Remove a user's subnetwork /// The caller must be root - #[pallet::call_index(115)] + #[pallet::call_index(120)] #[pallet::weight((Weight::from_parts(119_000_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index b70001c2b0..732f93d13d 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -6,7 +6,7 @@ use frame_system::Config; use sp_core::U256; use sp_std::collections::btree_map::BTreeMap; use substrate_fixed::types::{I96F32, U64F64, U96F32}; -use subtensor_runtime_common::TaoCurrency; +use subtensor_runtime_common::{NetUidStorageIndex, TaoCurrency}; use subtensor_swap_interface::{OrderType, SwapHandler}; #[test] @@ -289,7 +289,6 @@ fn dissolve_clears_all_per_subnet_storages() { SubnetOwner::::insert(net, owner_cold); SubnetOwnerHotkey::::insert(net, owner_hot); SubnetworkN::::insert(net, 0u16); - NetworkModality::::insert(net, 0u16); NetworksAdded::::insert(net, true); NetworkRegisteredAt::::insert(net, 0u64); @@ -298,11 +297,11 @@ fn dissolve_clears_all_per_subnet_storages() { Trust::::insert(net, vec![1u16]); Active::::insert(net, vec![true]); Emission::::insert(net, vec![AlphaCurrency::from(1)]); - Incentive::::insert(net, vec![1u16]); + Incentive::::insert(NetUidStorageIndex::from(net), vec![1u16]); Consensus::::insert(net, vec![1u16]); Dividends::::insert(net, vec![1u16]); PruningScores::::insert(net, vec![1u16]); - LastUpdate::::insert(net, vec![0u64]); + LastUpdate::::insert(NetUidStorageIndex::from(net), vec![0u64]); ValidatorPermit::::insert(net, vec![true]); ValidatorTrust::::insert(net, vec![1u16]); @@ -334,8 +333,8 @@ fn dissolve_clears_all_per_subnet_storages() { // Prefix / double-map collections Keys::::insert(net, 0u16, owner_hot); - Bonds::::insert(net, 0u16, vec![(0u16, 1u16)]); - Weights::::insert(net, 0u16, vec![(1u16, 1u16)]); + Bonds::::insert(NetUidStorageIndex::from(net), 0u16, vec![(0u16, 1u16)]); + Weights::::insert(NetUidStorageIndex::from(net), 0u16, vec![(1u16, 1u16)]); // Membership entry for the SAME hotkey as Keys IsNetworkMember::::insert(owner_hot, net, true); @@ -437,7 +436,6 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!SubnetOwner::::contains_key(net)); assert!(!SubnetOwnerHotkey::::contains_key(net)); assert!(!SubnetworkN::::contains_key(net)); - assert!(!NetworkModality::::contains_key(net)); assert!(!NetworksAdded::::contains_key(net)); assert!(!NetworkRegisteredAt::::contains_key(net)); @@ -446,11 +444,15 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!Trust::::contains_key(net)); assert!(!Active::::contains_key(net)); assert!(!Emission::::contains_key(net)); - assert!(!Incentive::::contains_key(net)); + assert!(!Incentive::::contains_key(NetUidStorageIndex::from( + net + ))); assert!(!Consensus::::contains_key(net)); assert!(!Dividends::::contains_key(net)); assert!(!PruningScores::::contains_key(net)); - assert!(!LastUpdate::::contains_key(net)); + assert!(!LastUpdate::::contains_key(NetUidStorageIndex::from( + net + ))); assert!(!ValidatorPermit::::contains_key(net)); assert!(!ValidatorTrust::::contains_key(net)); @@ -483,8 +485,16 @@ fn dissolve_clears_all_per_subnet_storages() { // Collections fully cleared assert!(Keys::::iter_prefix(net).next().is_none()); - assert!(Bonds::::iter_prefix(net).next().is_none()); - assert!(Weights::::iter_prefix(net).next().is_none()); + assert!( + Bonds::::iter_prefix(NetUidStorageIndex::from(net)) + .next() + .is_none() + ); + assert!( + Weights::::iter_prefix(NetUidStorageIndex::from(net)) + .next() + .is_none() + ); assert!(!IsNetworkMember::::contains_key(owner_hot, net)); // Token / price / provided reserves From ce11f6ba6c07395b3362ab6efb6d7b937fa3313c Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 15 Sep 2025 12:59:53 -0700 Subject: [PATCH 89/97] migrate_restore_subnet_locked --- pallets/subtensor/src/macros/hooks.rs | 4 +- .../src/migrations/migrate_subnet_locked.rs | 118 ++++++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/tests/migration.rs | 131 ++++++++++++++++++ 4 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/migrations/migrate_subnet_locked.rs diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 13713354c8..a3cb7a692f 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -145,7 +145,9 @@ mod hooks { // Migrate Subnet Limit .saturating_add(migrations::migrate_subnet_limit_to_default::migrate_subnet_limit_to_default::()) // Migrate Lock Reduction Interval - .saturating_add(migrations::migrate_network_lock_reduction_interval::migrate_network_lock_reduction_interval::()); + .saturating_add(migrations::migrate_network_lock_reduction_interval::migrate_network_lock_reduction_interval::()) + // Migrate subnet locked balances + .saturating_add(migrations::migrate_subnet_locked::migrate_restore_subnet_locked::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_subnet_locked.rs b/pallets/subtensor/src/migrations/migrate_subnet_locked.rs new file mode 100644 index 0000000000..40f199e8af --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_subnet_locked.rs @@ -0,0 +1,118 @@ +use super::*; +use frame_support::weights::Weight; +use log; +use scale_info::prelude::string::String; +use crate::{Config, HasMigrationRun, SubnetLocked, TaoCurrency}; +use subtensor_runtime_common::NetUid; + +pub fn migrate_restore_subnet_locked() -> Weight { + // Track whether we've already run this migration + let migration_name = b"migrate_restore_subnet_locked".to_vec(); + let mut weight = T::DbWeight::get().reads(1); + + if HasMigrationRun::::get(&migration_name) { + log::info!( + target: "runtime", + "Migration '{}' already run - skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + // (netuid, locked_rao) pairs taken from the historical snapshot (block #4_828_623). + const SUBNET_LOCKED: &[(u16, u64)] = &[ + ( 2, 976_893_069_056), + ( 3,2_569_362_397_490), + ( 4,1_928_551_593_932), + ( 5,1_712_540_082_588), + ( 6,1_495_929_556_770), + ( 7,1_011_702_451_936), + ( 8, 337_484_391_024), + ( 9, 381_240_180_320), + ( 10,1_253_515_128_353), + ( 11,1_453_924_672_132), + ( 12, 100_000_000_000), + ( 13, 100_000_000_000), + ( 14,1_489_714_521_808), + ( 15,1_784_089_225_496), + ( 16, 889_176_219_484), + ( 17,1_266_310_122_772), + ( 18, 222_355_058_433), + ( 19, 100_000_000_000), + ( 20, 100_000_000_000), + ( 21, 885_096_322_978), + ( 22, 100_000_000_000), + ( 23, 100_000_000_000), + ( 24,5_146_073_854_481), + ( 25,1_782_920_948_214), + ( 26, 153_583_865_248), + ( 27, 201_344_183_084), + ( 28, 901_455_879_445), + ( 29, 175_000_001_600), + ( 30,1_419_730_660_074), + ( 31, 319_410_100_502), + ( 32,2_016_397_028_246), + ( 33,1_626_477_274_174), + ( 34,1_455_297_496_345), + ( 35,1_191_275_979_639), + ( 36,1_097_008_574_216), + ( 37, 864_664_455_362), + ( 38,1_001_936_494_076), + ( 39,1_366_096_404_884), + ( 40, 100_000_000_000), + ( 41, 535_937_523_200), + ( 42,1_215_698_423_344), + ( 43,1_641_308_676_800), + ( 44,1_514_636_189_434), + ( 45,1_605_608_381_438), + ( 46,1_095_943_027_350), + ( 47,1_499_235_469_986), + ( 48,1_308_073_720_362), + ( 49,1_222_672_092_068), + ( 50,2_628_355_421_561), + ( 51,1_520_860_720_561), + ( 52,1_794_457_248_725), + ( 53,1_721_472_811_492), + ( 54,2_048_900_691_868), + ( 55,1_278_597_446_119), + ( 56,2_016_045_544_480), + ( 57,1_920_563_399_676), + ( 58,2_246_525_691_504), + ( 59,1_776_159_384_888), + ( 60,2_173_138_865_414), + ( 61,1_435_634_867_728), + ( 62,2_061_282_563_888), + ( 63,3_008_967_320_998), + ( 64,2_099_236_359_026), + ]; + + let mut inserted: u32 = 0; + let mut total_rao: u128 = 0; + + // ── 1) Re-insert the historical values ──────────────────────────────── + for &(netuid_u16, amount_rao_u64) in SUBNET_LOCKED.iter() { + let key: NetUid = NetUid::from(netuid_u16); + let amount: TaoCurrency = TaoCurrency::from(amount_rao_u64); + + SubnetLocked::::insert(key, amount); + + inserted = inserted.saturating_add(1); + total_rao = total_rao.saturating_add(amount_rao_u64 as u128); + + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + } + + // ── 2) Mark migration done ──────────────────────────────────────────── + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + target: "runtime", + "Migration '{}' completed - inserted {} SubnetLocked entries; total≈{} RAO.", + String::from_utf8_lossy(&migration_name), + inserted, + total_rao + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index f6339dc266..e7c50c0080 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -42,6 +42,7 @@ pub mod migrate_set_subtoken_enabled; pub mod migrate_stake_threshold; pub mod migrate_subnet_identities_to_v3; pub mod migrate_subnet_limit_to_default; +pub mod migrate_subnet_locked; pub mod migrate_subnet_symbols; pub mod migrate_subnet_volume; pub mod migrate_to_v1_separate_emission; diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index b09420ae8a..f052d70e96 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -1783,3 +1783,134 @@ fn test_migrate_network_lock_reduction_interval_and_decay() { ); }); } + +#[test] +fn test_migrate_restore_subnet_locked_feb1_2025() { + use sp_runtime::traits::SaturatedConversion; // only for NetUid -> u16 when reading back + use std::collections::BTreeMap; + + use crate::{HasMigrationRun, SubnetLocked, TaoCurrency}; + + // NOTE: Ensure the migration uses `TaoCurrency::from(rao_u64)` and a `&[(u16, u64)]` snapshot. + new_test_ext(0).execute_with(|| { + // ── pre ────────────────────────────────────────────────────────────── + let name = b"migrate_restore_subnet_locked".to_vec(); + assert!( + !HasMigrationRun::::get(name.clone()), + "HasMigrationRun should be false before migration" + ); + + // Snapshot at block #4_828_623 (2025-02-01 00:00:00Z), RAO as u64. + const EXPECTED: &[(u16, u64)] = &[ + (2, 976_893_069_056), + (3, 2_569_362_397_490), + (4, 1_928_551_593_932), + (5, 1_712_540_082_588), + (6, 1_495_929_556_770), + (7, 1_011_702_451_936), + (8, 337_484_391_024), + (9, 381_240_180_320), + (10, 1_253_515_128_353), + (11, 1_453_924_672_132), + (12, 100_000_000_000), + (13, 100_000_000_000), + (14, 1_489_714_521_808), + (15, 1_784_089_225_496), + (16, 889_176_219_484), + (17, 1_266_310_122_772), + (18, 222_355_058_433), + (19, 100_000_000_000), + (20, 100_000_000_000), + (21, 885_096_322_978), + (22, 100_000_000_000), + (23, 100_000_000_000), + (24, 5_146_073_854_481), + (25, 1_782_920_948_214), + (26, 153_583_865_248), + (27, 201_344_183_084), + (28, 901_455_879_445), + (29, 175_000_001_600), + (30, 1_419_730_660_074), + (31, 319_410_100_502), + (32, 2_016_397_028_246), + (33, 1_626_477_274_174), + (34, 1_455_297_496_345), + (35, 1_191_275_979_639), + (36, 1_097_008_574_216), + (37, 864_664_455_362), + (38, 1_001_936_494_076), + (39, 1_366_096_404_884), + (40, 100_000_000_000), + (41, 535_937_523_200), + (42, 1_215_698_423_344), + (43, 1_641_308_676_800), + (44, 1_514_636_189_434), + (45, 1_605_608_381_438), + (46, 1_095_943_027_350), + (47, 1_499_235_469_986), + (48, 1_308_073_720_362), + (49, 1_222_672_092_068), + (50, 2_628_355_421_561), + (51, 1_520_860_720_561), + (52, 1_794_457_248_725), + (53, 1_721_472_811_492), + (54, 2_048_900_691_868), + (55, 1_278_597_446_119), + (56, 2_016_045_544_480), + (57, 1_920_563_399_676), + (58, 2_246_525_691_504), + (59, 1_776_159_384_888), + (60, 2_173_138_865_414), + (61, 1_435_634_867_728), + (62, 2_061_282_563_888), + (63, 3_008_967_320_998), + (64, 2_099_236_359_026), + ]; + + // ── run migration ──────────────────────────────────────────────────── + let weight = + crate::migrations::migrate_subnet_locked::migrate_restore_subnet_locked::(); + assert!(!weight.is_zero(), "migration weight should be > 0"); + + // ── validate: build a (u16 -> u64) map directly from storage iterator ─ + let actual: BTreeMap = SubnetLocked::::iter() + .map(|(k, v)| (k.saturated_into::(), u64::from(v))) + .collect(); + + let expected: BTreeMap = EXPECTED.iter().copied().collect(); + + // 1) exact content match (keys and values) + assert_eq!(actual, expected, "SubnetLocked map mismatch with snapshot"); + + // 2) count and total sum match expectations + let expected_len = expected.len(); + let expected_sum: u128 = expected.values().map(|v| *v as u128).sum(); + + let count_after = actual.len(); + let sum_after: u128 = actual.values().map(|v| *v as u128).sum(); + + assert_eq!(count_after, expected_len, "entry count mismatch"); + assert_eq!(sum_after, expected_sum, "total RAO sum mismatch"); + + // ── migration flag ─────────────────────────────────────────────────── + assert!( + HasMigrationRun::::get(name.clone()), + "HasMigrationRun should be true after migration" + ); + + // ── idempotence: re-running does not change storage ───────────────── + let before = actual; + + let _again = + crate::migrations::migrate_subnet_locked::migrate_restore_subnet_locked::(); + + let after: BTreeMap = SubnetLocked::::iter() + .map(|(k, v)| (k.saturated_into::(), u64::from(v))) + .collect(); + + assert_eq!( + before, after, + "re-running the migration should not change storage" + ); + }); +} From 13b23fd7a292a64dd3ea3ad81970af1b1824a0d4 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:46:11 -0700 Subject: [PATCH 90/97] clear new subsubnet maps --- pallets/subtensor/src/coinbase/root.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 03a82cac93..e3acbf3432 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -545,6 +545,8 @@ impl Pallet { let _ = Weights::::clear_prefix(netuid_index, u32::MAX, None); } RevealPeriodEpochs::::remove(netuid); + SubsubnetCountCurrent::::remove(netuid); + SubsubnetEmissionSplit::::remove(netuid); // Last hotkey swap (DMAP where netuid is FIRST key → easy) let _ = LastHotkeySwapOnNetuid::::clear_prefix(netuid, u32::MAX, None); From 60834c828220a31e148a40a0fb60f4770a73cb7e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:44:20 -0700 Subject: [PATCH 91/97] add NetworkRegistrationStartBlock --- pallets/subtensor/src/lib.rs | 10 ++ ...migrate_network_lock_reduction_interval.rs | 16 ++- .../src/migrations/migrate_subnet_locked.rs | 128 +++++++++--------- pallets/subtensor/src/subnets/subnet.rs | 7 +- pallets/subtensor/src/tests/migration.rs | 42 +++++- scripts/benchmark_action.sh | 2 +- 6 files changed, 130 insertions(+), 75 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 29e11d62cf..331ca94e6c 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -551,6 +551,11 @@ pub mod pallet { T::InitialNetworkRateLimit::get() } #[pallet::type_value] + /// Default value for network rate limit. + pub fn DefaultNetworkRegistrationStartBlock() -> u64 { + 0 + } + #[pallet::type_value] /// Default value for weights version key rate limit. /// In units of tempos. pub fn DefaultWeightsVersionKeyRateLimit() -> u64 { @@ -1833,6 +1838,11 @@ pub mod pallet { pub type CommitRevealWeightsVersion = StorageValue<_, u16, ValueQuery, DefaultCommitRevealWeightsVersion>; + #[pallet::storage] + /// ITEM( NetworkRegistrationStartBlock ) + pub type NetworkRegistrationStartBlock = + StorageValue<_, u64, ValueQuery, DefaultNetworkRegistrationStartBlock>; + /// ====================== /// ==== Sub-subnets ===== /// ====================== diff --git a/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs b/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs index 56079b0a13..c19e16067e 100644 --- a/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs +++ b/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs @@ -5,6 +5,7 @@ use scale_info::prelude::string::String; pub fn migrate_network_lock_reduction_interval() -> Weight { const NEW_VALUE: u64 = 28_800; + const ONE_WEEK_BLOCKS: u64 = 50_400; let migration_name = b"migrate_network_lock_reduction_interval".to_vec(); let mut weight = T::DbWeight::get().reads(1); @@ -19,6 +20,8 @@ pub fn migrate_network_lock_reduction_interval() -> Weight { return weight; } + let current_block = Pallet::::get_current_block_as_u64(); + // ── 1) Set new values ───────────────────────────────────────────────── NetworkLockReductionInterval::::put(NEW_VALUE); weight = weight.saturating_add(T::DbWeight::get().writes(1)); @@ -29,7 +32,12 @@ pub fn migrate_network_lock_reduction_interval() -> Weight { Pallet::::set_network_last_lock(TaoCurrency::from(1_000_000_000_000)); weight = weight.saturating_add(T::DbWeight::get().writes(1)); - Pallet::::set_network_last_lock_block(Pallet::::get_current_block_as_u64()); + // Hold price at 2000 TAO until day 7, then begin linear decay + Pallet::::set_network_last_lock_block(current_block.saturating_add(ONE_WEEK_BLOCKS)); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // Allow registrations starting at day 7 + NetworkRegistrationStartBlock::::put(current_block.saturating_add(ONE_WEEK_BLOCKS)); weight = weight.saturating_add(T::DbWeight::get().writes(1)); // ── 2) Mark migration done ─────────────────────────────────────────── @@ -38,9 +46,11 @@ pub fn migrate_network_lock_reduction_interval() -> Weight { log::info!( target: "runtime", - "Migration '{}' completed - NetworkLockReductionInterval => {}.", + "Migration '{}' completed - NetworkLockReductionInterval & NetworkRateLimit => {}. \ + last_lock set to 1_000_000_000_000 rao; last_lock_block/start_block => {}.", String::from_utf8_lossy(&migration_name), - NEW_VALUE + NEW_VALUE, + current_block.saturating_add(ONE_WEEK_BLOCKS), ); weight diff --git a/pallets/subtensor/src/migrations/migrate_subnet_locked.rs b/pallets/subtensor/src/migrations/migrate_subnet_locked.rs index 40f199e8af..e72881ea7d 100644 --- a/pallets/subtensor/src/migrations/migrate_subnet_locked.rs +++ b/pallets/subtensor/src/migrations/migrate_subnet_locked.rs @@ -1,8 +1,8 @@ use super::*; +use crate::{Config, HasMigrationRun, SubnetLocked, TaoCurrency}; use frame_support::weights::Weight; use log; use scale_info::prelude::string::String; -use crate::{Config, HasMigrationRun, SubnetLocked, TaoCurrency}; use subtensor_runtime_common::NetUid; pub fn migrate_restore_subnet_locked() -> Weight { @@ -21,69 +21,69 @@ pub fn migrate_restore_subnet_locked() -> Weight { // (netuid, locked_rao) pairs taken from the historical snapshot (block #4_828_623). const SUBNET_LOCKED: &[(u16, u64)] = &[ - ( 2, 976_893_069_056), - ( 3,2_569_362_397_490), - ( 4,1_928_551_593_932), - ( 5,1_712_540_082_588), - ( 6,1_495_929_556_770), - ( 7,1_011_702_451_936), - ( 8, 337_484_391_024), - ( 9, 381_240_180_320), - ( 10,1_253_515_128_353), - ( 11,1_453_924_672_132), - ( 12, 100_000_000_000), - ( 13, 100_000_000_000), - ( 14,1_489_714_521_808), - ( 15,1_784_089_225_496), - ( 16, 889_176_219_484), - ( 17,1_266_310_122_772), - ( 18, 222_355_058_433), - ( 19, 100_000_000_000), - ( 20, 100_000_000_000), - ( 21, 885_096_322_978), - ( 22, 100_000_000_000), - ( 23, 100_000_000_000), - ( 24,5_146_073_854_481), - ( 25,1_782_920_948_214), - ( 26, 153_583_865_248), - ( 27, 201_344_183_084), - ( 28, 901_455_879_445), - ( 29, 175_000_001_600), - ( 30,1_419_730_660_074), - ( 31, 319_410_100_502), - ( 32,2_016_397_028_246), - ( 33,1_626_477_274_174), - ( 34,1_455_297_496_345), - ( 35,1_191_275_979_639), - ( 36,1_097_008_574_216), - ( 37, 864_664_455_362), - ( 38,1_001_936_494_076), - ( 39,1_366_096_404_884), - ( 40, 100_000_000_000), - ( 41, 535_937_523_200), - ( 42,1_215_698_423_344), - ( 43,1_641_308_676_800), - ( 44,1_514_636_189_434), - ( 45,1_605_608_381_438), - ( 46,1_095_943_027_350), - ( 47,1_499_235_469_986), - ( 48,1_308_073_720_362), - ( 49,1_222_672_092_068), - ( 50,2_628_355_421_561), - ( 51,1_520_860_720_561), - ( 52,1_794_457_248_725), - ( 53,1_721_472_811_492), - ( 54,2_048_900_691_868), - ( 55,1_278_597_446_119), - ( 56,2_016_045_544_480), - ( 57,1_920_563_399_676), - ( 58,2_246_525_691_504), - ( 59,1_776_159_384_888), - ( 60,2_173_138_865_414), - ( 61,1_435_634_867_728), - ( 62,2_061_282_563_888), - ( 63,3_008_967_320_998), - ( 64,2_099_236_359_026), + (2, 976_893_069_056), + (3, 2_569_362_397_490), + (4, 1_928_551_593_932), + (5, 1_712_540_082_588), + (6, 1_495_929_556_770), + (7, 1_011_702_451_936), + (8, 337_484_391_024), + (9, 381_240_180_320), + (10, 1_253_515_128_353), + (11, 1_453_924_672_132), + (12, 100_000_000_000), + (13, 100_000_000_000), + (14, 1_489_714_521_808), + (15, 1_784_089_225_496), + (16, 889_176_219_484), + (17, 1_266_310_122_772), + (18, 222_355_058_433), + (19, 100_000_000_000), + (20, 100_000_000_000), + (21, 885_096_322_978), + (22, 100_000_000_000), + (23, 100_000_000_000), + (24, 5_146_073_854_481), + (25, 1_782_920_948_214), + (26, 153_583_865_248), + (27, 201_344_183_084), + (28, 901_455_879_445), + (29, 175_000_001_600), + (30, 1_419_730_660_074), + (31, 319_410_100_502), + (32, 2_016_397_028_246), + (33, 1_626_477_274_174), + (34, 1_455_297_496_345), + (35, 1_191_275_979_639), + (36, 1_097_008_574_216), + (37, 864_664_455_362), + (38, 1_001_936_494_076), + (39, 1_366_096_404_884), + (40, 100_000_000_000), + (41, 535_937_523_200), + (42, 1_215_698_423_344), + (43, 1_641_308_676_800), + (44, 1_514_636_189_434), + (45, 1_605_608_381_438), + (46, 1_095_943_027_350), + (47, 1_499_235_469_986), + (48, 1_308_073_720_362), + (49, 1_222_672_092_068), + (50, 2_628_355_421_561), + (51, 1_520_860_720_561), + (52, 1_794_457_248_725), + (53, 1_721_472_811_492), + (54, 2_048_900_691_868), + (55, 1_278_597_446_119), + (56, 2_016_045_544_480), + (57, 1_920_563_399_676), + (58, 2_246_525_691_504), + (59, 1_776_159_384_888), + (60, 2_173_138_865_414), + (61, 1_435_634_867_728), + (62, 2_061_282_563_888), + (63, 3_008_967_320_998), + (64, 2_099_236_359_026), ]; let mut inserted: u32 = 0; diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 3719a40f8d..d3eefc16dc 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -128,8 +128,13 @@ impl Pallet { // --- 3. Ensure the mechanism is Dynamic. ensure!(mechid == 1, Error::::MechanismDoesNotExist); - // --- 4. Rate limit for network registrations. let current_block = Self::get_current_block_as_u64(); + ensure!( + current_block >= NetworkRegistrationStartBlock::::get(), + Error::::SubNetRegistrationDisabled + ); + + // --- 4. Rate limit for network registrations. ensure!( Self::passes_rate_limit(&TransactionType::RegisterNetwork, &coldkey), Error::::NetworkTxRateLimitExceeded diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index f052d70e96..d99625ece2 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -1757,26 +1757,56 @@ fn test_migrate_subnet_limit_to_default() { #[test] fn test_migrate_network_lock_reduction_interval_and_decay() { new_test_ext(0).execute_with(|| { + const NEW_VALUE: u64 = 28_800; + const ONE_WEEK_BLOCKS: u64 = 50_400; + // ── pre ────────────────────────────────────────────────────────────── assert!( !HasMigrationRun::::get(b"migrate_network_lock_reduction_interval".to_vec()), "HasMigrationRun should be false before migration" ); - // ensure current_block > 0 so mult = 2 after migration + // ensure current_block > 0 step_block(1); + let current_block_before = Pallet::::get_current_block_as_u64(); // ── run migration ──────────────────────────────────────────────────── let weight = crate::migrations::migrate_network_lock_reduction_interval::migrate_network_lock_reduction_interval::(); assert!(!weight.is_zero(), "migration weight should be > 0"); // ── params & flags ─────────────────────────────────────────────────── - assert_eq!(NetworkLockReductionInterval::::get(), 28_800); - assert_eq!(NetworkRateLimit::::get(), 28_800); - assert_eq!(Pallet::::get_network_last_lock(), 1_000_000_000_000u64.into()); // 1000 TAO in rAO + assert_eq!(NetworkLockReductionInterval::::get(), NEW_VALUE); + assert_eq!(NetworkRateLimit::::get(), NEW_VALUE); + assert_eq!( + Pallet::::get_network_last_lock(), + 1_000_000_000_000u64.into(), // 1000 TAO in rao + "last_lock should be 1_000_000_000_000 rao" + ); + + // last_lock_block should be set one week in the future + let last_lock_block = Pallet::::get_network_last_lock_block(); + let expected_block = current_block_before.saturating_add(ONE_WEEK_BLOCKS); + assert_eq!( + last_lock_block, + expected_block, + "last_lock_block should be current + ONE_WEEK_BLOCKS" + ); + + // registration start block should match the same future block + assert_eq!( + NetworkRegistrationStartBlock::::get(), + expected_block, + "NetworkRegistrationStartBlock should equal last_lock_block" + ); + + // lock cost should be 2000 TAO immediately after migration + let lock_cost_now = Pallet::::get_network_lock_cost(); + assert_eq!( + lock_cost_now, + 2_000_000_000_000u64.into(), + "lock cost should be 2000 TAO right after migration" + ); - let start_block = Pallet::::get_network_last_lock_block(); - assert_eq!(start_block, Pallet::::get_current_block_as_u64()); assert!( HasMigrationRun::::get(b"migrate_network_lock_reduction_interval".to_vec()), "HasMigrationRun should be true after migration" diff --git a/scripts/benchmark_action.sh b/scripts/benchmark_action.sh index 105cbe9bf5..a475211163 100755 --- a/scripts/benchmark_action.sh +++ b/scripts/benchmark_action.sh @@ -11,7 +11,7 @@ declare -A DISPATCH_PATHS=( [swap]="../pallets/swap/src/pallet/mod.rs" ) -THRESHOLD=20 +THRESHOLD=40 MAX_RETRIES=3 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" From 1005c974c1e3a1535eadaa211d54e9dd0764f8af Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:36:52 -0700 Subject: [PATCH 92/97] clippy --- pallets/subtensor/src/staking/remove_stake.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index d3834c283a..8a691a9866 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -476,10 +476,7 @@ impl Pallet { Ok(sim) => TaoCurrency::from(sim.amount_paid_out), Err(e) => { log::debug!( - "destroy_alpha_in_out_stakes: sim_swap owner α→τ failed (netuid={:?}, alpha={}, err={:?}); falling back to price multiply.", - netuid, - owner_alpha_u64, - e + "destroy_alpha_in_out_stakes: sim_swap owner α→τ failed (netuid={netuid:?}, alpha={owner_alpha_u64}, err={e:?}); falling back to price multiply.", ); let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); let val_u64: u64 = U96F32::from_num(owner_alpha_u64) From eb1110697fb6e02a1ef2109eeac6f45452fe0f23 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:07:32 -0700 Subject: [PATCH 93/97] remove unused --- common/src/lib.rs | 1 - pallets/subtensor/src/lib.rs | 3 --- pallets/swap/src/mock.rs | 4 ---- 3 files changed, 8 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 3fc40f695a..347d2dbed4 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -175,7 +175,6 @@ pub trait SubnetInfo { fn mechanism(netuid: NetUid) -> u16; fn is_owner(account_id: &AccountId, netuid: NetUid) -> bool; fn is_subtoken_enabled(netuid: NetUid) -> bool; - fn get_owned_hotkeys(coldkey: &AccountId) -> Vec; } pub trait BalanceOps { diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f8e8271e91..0d8537b64b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2119,9 +2119,6 @@ impl> fn is_subtoken_enabled(netuid: NetUid) -> bool { SubtokenEnabled::::get(netuid) } - fn get_owned_hotkeys(coldkey: &T::AccountId) -> Vec { - OwnedHotkeys::::get(coldkey) - } } impl> diff --git a/pallets/swap/src/mock.rs b/pallets/swap/src/mock.rs index 9653f9ee22..40aac6d796 100644 --- a/pallets/swap/src/mock.rs +++ b/pallets/swap/src/mock.rs @@ -120,10 +120,6 @@ impl SubnetInfo for MockLiquidityProvider { fn is_subtoken_enabled(netuid: NetUid) -> bool { netuid.inner() != SUBTOKEN_DISABLED_NETUID } - - fn get_owned_hotkeys(_coldkey: &AccountId) -> Vec { - Vec::::new() - } } pub struct MockBalanceOps; From d7dcbd8de02f8176cd17f028bd223545f0cc9bfc Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 16 Sep 2025 12:19:07 -0700 Subject: [PATCH 94/97] 8 day NetworkLockReductionInterval --- .../migrate_network_lock_reduction_interval.rs | 12 +++++------- pallets/subtensor/src/tests/migration.rs | 7 ++++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs b/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs index c19e16067e..99bb5b6e97 100644 --- a/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs +++ b/pallets/subtensor/src/migrations/migrate_network_lock_reduction_interval.rs @@ -4,7 +4,8 @@ use log; use scale_info::prelude::string::String; pub fn migrate_network_lock_reduction_interval() -> Weight { - const NEW_VALUE: u64 = 28_800; + const FOUR_DAYS: u64 = 28_800; + const EIGHT_DAYS: u64 = 57_600; const ONE_WEEK_BLOCKS: u64 = 50_400; let migration_name = b"migrate_network_lock_reduction_interval".to_vec(); @@ -23,10 +24,10 @@ pub fn migrate_network_lock_reduction_interval() -> Weight { let current_block = Pallet::::get_current_block_as_u64(); // ── 1) Set new values ───────────────────────────────────────────────── - NetworkLockReductionInterval::::put(NEW_VALUE); + NetworkLockReductionInterval::::put(EIGHT_DAYS); weight = weight.saturating_add(T::DbWeight::get().writes(1)); - NetworkRateLimit::::put(NEW_VALUE); + NetworkRateLimit::::put(FOUR_DAYS); weight = weight.saturating_add(T::DbWeight::get().writes(1)); Pallet::::set_network_last_lock(TaoCurrency::from(1_000_000_000_000)); @@ -46,11 +47,8 @@ pub fn migrate_network_lock_reduction_interval() -> Weight { log::info!( target: "runtime", - "Migration '{}' completed - NetworkLockReductionInterval & NetworkRateLimit => {}. \ - last_lock set to 1_000_000_000_000 rao; last_lock_block/start_block => {}.", + "Migration '{}' completed.", String::from_utf8_lossy(&migration_name), - NEW_VALUE, - current_block.saturating_add(ONE_WEEK_BLOCKS), ); weight diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index d99625ece2..1e58d2f7ab 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -1757,7 +1757,8 @@ fn test_migrate_subnet_limit_to_default() { #[test] fn test_migrate_network_lock_reduction_interval_and_decay() { new_test_ext(0).execute_with(|| { - const NEW_VALUE: u64 = 28_800; + const FOUR_DAYS: u64 = 28_800; + const EIGHT_DAYS: u64 = 57_600; const ONE_WEEK_BLOCKS: u64 = 50_400; // ── pre ────────────────────────────────────────────────────────────── @@ -1775,8 +1776,8 @@ fn test_migrate_network_lock_reduction_interval_and_decay() { assert!(!weight.is_zero(), "migration weight should be > 0"); // ── params & flags ─────────────────────────────────────────────────── - assert_eq!(NetworkLockReductionInterval::::get(), NEW_VALUE); - assert_eq!(NetworkRateLimit::::get(), NEW_VALUE); + assert_eq!(NetworkLockReductionInterval::::get(), EIGHT_DAYS); + assert_eq!(NetworkRateLimit::::get(), FOUR_DAYS); assert_eq!( Pallet::::get_network_last_lock(), 1_000_000_000_000u64.into(), // 1000 TAO in rao From b6a57500eb4c924288cbc812d598e679176e5689 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 16 Sep 2025 12:25:42 -0700 Subject: [PATCH 95/97] clippy --- common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 347d2dbed4..26aa6b2f13 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -9,7 +9,7 @@ use runtime_common::prod_or_fast; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{ - MultiSignature, Vec, + MultiSignature, traits::{IdentifyAccount, Verify}, }; use subtensor_macros::freeze_struct; From ab23480f919863dbc75c9ca80b825794ac44634e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 17 Sep 2025 10:44:41 -0700 Subject: [PATCH 96/97] continue on error --- pallets/swap/src/pallet/impls.rs | 44 +++++++++++++++++--------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index c3652b10c9..ce4336df78 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1215,9 +1215,6 @@ impl Pallet { /// Dissolve all LPs and clean state. pub fn do_dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { - let user_lp_enabled = - >::is_user_liquidity_enabled(netuid); - if SwapV3Initialized::::get(netuid) { // 1) Snapshot (owner, position_id). struct CloseItem { @@ -1236,21 +1233,28 @@ impl Pallet { .sort_by(|a, b| (a.owner == protocol_account).cmp(&(b.owner == protocol_account))); for CloseItem { owner, pos_id } in to_close.into_iter() { - let rm = Self::do_remove_liquidity(netuid, &owner, pos_id)?; - - // τ: refund **principal only** (no τ fees). - if rm.tao > TaoCurrency::ZERO { - T::BalanceOps::increase_balance(&owner, rm.tao); - } - - if owner != protocol_account { - // Principal reserves decrease - T::BalanceOps::decrease_provided_tao_reserve(netuid, rm.tao); - - // Burn α (principal + fees) from provided reserves; do not credit to users. - let alpha_burn = rm.alpha.saturating_add(rm.fee_alpha); - if alpha_burn > AlphaCurrency::ZERO { - T::BalanceOps::decrease_provided_alpha_reserve(netuid, alpha_burn); + match Self::do_remove_liquidity(netuid, &owner, pos_id) { + Ok(rm) => { + if rm.tao > TaoCurrency::ZERO { + T::BalanceOps::increase_balance(&owner, rm.tao); + } + if owner != protocol_account { + T::BalanceOps::decrease_provided_tao_reserve(netuid, rm.tao); + let alpha_burn = rm.alpha.saturating_add(rm.fee_alpha); + if alpha_burn > AlphaCurrency::ZERO { + T::BalanceOps::decrease_provided_alpha_reserve(netuid, alpha_burn); + } + } + } + Err(e) => { + log::debug!( + "dissolve_all_lp: force-closing failed position: netuid={:?}, owner={:?}, pos_id={:?}, err={:?}", + netuid, + owner, + pos_id, + e + ); + continue; } } } @@ -1277,7 +1281,7 @@ impl Pallet { EnabledUserLiquidity::::remove(netuid); log::debug!( - "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V3, user_lp_enabled={user_lp_enabled}, positions closed; τ principal refunded; α burned; state cleared" + "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V3, positions closed; τ principal refunded; α burned; state cleared" ); return Ok(()); @@ -1305,7 +1309,7 @@ impl Pallet { EnabledUserLiquidity::::remove(netuid); log::debug!( - "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V2-or-nonV3, user_lp_enabled={user_lp_enabled}, state_cleared" + "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V2-or-nonV3, state_cleared" ); Ok(()) From 4ce7b38cdcb25c9d2e627be3df68ea81d59f8492 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 17 Sep 2025 10:54:52 -0700 Subject: [PATCH 97/97] clippy --- pallets/swap/src/pallet/impls.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index ce4336df78..c0a109bfb5 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1248,11 +1248,7 @@ impl Pallet { } Err(e) => { log::debug!( - "dissolve_all_lp: force-closing failed position: netuid={:?}, owner={:?}, pos_id={:?}, err={:?}", - netuid, - owner, - pos_id, - e + "dissolve_all_lp: force-closing failed position: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, err={e:?}" ); continue; }