From f6379754c1f056f10ee00676dd0820a93ce59509 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 25 Mar 2026 15:33:30 -0400 Subject: [PATCH 1/7] Add hk swap test with preexisting stake of new hotkey --- .../src/tests/swap_hotkey_with_subnet.rs | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index eb310d1202..8091da647e 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -2546,3 +2546,150 @@ fn test_revert_claim_root_with_swap_hotkey() { ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_swap_hotkey_with_existing_stake --exact --show-output +#[test] +fn test_swap_hotkey_with_existing_stake() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(4); + let staker1 = U256::from(5); + let staker2 = U256::from(6); + let subnet_owner_coldkey = U256::from(1000); + let subnet_owner_hotkey = U256::from(1001); + let staked_tao_1 = DefaultMinStake::::get().to_u64() * 321; + let staked_tao_2 = DefaultMinStake::::get().to_u64() * 123; + let staked_tao_3 = DefaultMinStake::::get().to_u64() * 234; + let staked_tao_4 = DefaultMinStake::::get().to_u64() * 156; + + // Set up initial state + let netuid = add_dynamic_network(&subnet_owner_coldkey, &subnet_owner_hotkey); + register_ok_neuron(netuid, old_hotkey, coldkey, 1234); + register_ok_neuron(netuid, new_hotkey, coldkey, 1234); + + // Add balance to coldkeys + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 10_000_000_000_u64.into()); + SubtensorModule::add_balance_to_coldkey_account(&staker1, 10_000_000_000_u64.into()); + SubtensorModule::add_balance_to_coldkey_account(&staker2, 10_000_000_000_u64.into()); + + // Stake with staker1 coldkey on old_hotkey + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(staker1), + old_hotkey, + netuid, + staked_tao_1.into() + )); + + // Stake with staker2 coldkey on old_hotkey + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(staker2), + old_hotkey, + netuid, + staked_tao_2.into() + )); + + // Stake with staker1 coldkey on new_hotkey + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(staker1), + new_hotkey, + netuid, + staked_tao_3.into() + )); + + // Stake with staker2 coldkey on new_hotkey + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(staker2), + new_hotkey, + netuid, + staked_tao_4.into() + )); + + // Hotkey new_hotkey gets deregistered, stake stays + IsNetworkMember::::remove(new_hotkey, netuid); + + let hk1_stake_1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &staker1, + netuid, + ); + let hk2_stake_1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &staker1, + netuid, + ); + let hk1_stake_2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &staker2, + netuid, + ); + let hk2_stake_2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &staker2, + netuid, + ); + + assert!(!hk1_stake_1.is_zero()); + assert!(!hk2_stake_1.is_zero()); + assert!(!hk1_stake_2.is_zero()); + assert!(!hk2_stake_2.is_zero()); + + let total_hk1_stake = SubtensorModule::get_total_stake_for_hotkey(&old_hotkey); + let total_hk2_stake = SubtensorModule::get_total_stake_for_hotkey(&new_hotkey); + assert!(!total_hk1_stake.is_zero()); + assert!(!total_hk2_stake.is_zero()); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid), + false + )); + + // Check correctness of stake transfer + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &staker1, + netuid + ), + 0.into() + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &staker2, + netuid + ), + 0.into() + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &staker1, + netuid + ), + hk2_stake_1 + hk1_stake_1 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &staker2, + netuid + ), + hk2_stake_2 + hk1_stake_2 + ); + + // Check total stake transfer + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), + 0.into() + ); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), + total_hk1_stake + total_hk2_stake + ); + }); +} \ No newline at end of file From b9c13e388ae8b8ee9daf05225419b86848d07e10 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 25 Mar 2026 16:00:23 -0400 Subject: [PATCH 2/7] Fix test with existing stake --- .../subtensor/src/tests/swap_hotkey_with_subnet.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index 8091da647e..fdcbadf408 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -2558,10 +2558,10 @@ fn test_swap_hotkey_with_existing_stake() { let staker2 = U256::from(6); let subnet_owner_coldkey = U256::from(1000); let subnet_owner_hotkey = U256::from(1001); - let staked_tao_1 = DefaultMinStake::::get().to_u64() * 321; - let staked_tao_2 = DefaultMinStake::::get().to_u64() * 123; - let staked_tao_3 = DefaultMinStake::::get().to_u64() * 234; - let staked_tao_4 = DefaultMinStake::::get().to_u64() * 156; + let staked_tao_1 = 100_000_000; + let staked_tao_2 = 200_000_000; + let staked_tao_3 = 300_000_000; + let staked_tao_4 = 500_000_000; // Set up initial state let netuid = add_dynamic_network(&subnet_owner_coldkey, &subnet_owner_hotkey); @@ -2605,6 +2605,11 @@ fn test_swap_hotkey_with_existing_stake() { staked_tao_4.into() )); + // Emulate effect of emission into alpha pool - makes numerators and denominators not equal to alpha + let emission = AlphaBalance::from(1_000_000_000); + SubtensorModule::increase_stake_for_hotkey_on_subnet(&old_hotkey, netuid, emission); + SubtensorModule::increase_stake_for_hotkey_on_subnet(&new_hotkey, netuid, emission); + // Hotkey new_hotkey gets deregistered, stake stays IsNetworkMember::::remove(new_hotkey, netuid); From 1ec677e1cc0153833ce3e466c697be899b8ba818 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 25 Mar 2026 17:44:19 -0400 Subject: [PATCH 3/7] Fix stake swap in hotkey swap --- pallets/subtensor/src/swap/swap_hotkey.rs | 71 +++++++------------ pallets/subtensor/src/tests/swap_hotkey.rs | 9 ++- .../src/tests/swap_hotkey_with_subnet.rs | 18 ++--- 3 files changed, 40 insertions(+), 58 deletions(-) diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 1138ed1cde..d3527e944f 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -360,37 +360,6 @@ impl Pallet { netuid: NetUid, keep_stake: bool, ) -> DispatchResult { - if !keep_stake { - // 1. Swap total hotkey alpha for all subnets it exists on. - // TotalHotkeyAlpha( hotkey, netuid ) -> alpha -- the total alpha that the hotkey has on a specific subnet. - let alpha = TotalHotkeyAlpha::::take(old_hotkey, netuid); - - TotalHotkeyAlpha::::mutate(new_hotkey, netuid, |value| { - *value = value.saturating_add(alpha) - }); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - - // 2. Swap total hotkey shares on all subnets it exists on. - // TotalHotkeyShares( hotkey, netuid ) -> share pool denominator for this hotkey on this subnet. - // Merge v1 and v2 TotalHotkeyShares because TotalHotkeyShares v1 is deprecated - weight.saturating_accrue(T::DbWeight::get().reads(4)); - let old_share_v1 = SafeFloat::from(TotalHotkeyShares::::take(old_hotkey, netuid)); - let old_share_v2 = TotalHotkeySharesV2::::take(old_hotkey, netuid); - let total_old_shares = old_share_v1.add(&old_share_v2).unwrap_or_default(); - - let new_share_v1 = SafeFloat::from(TotalHotkeyShares::::take(new_hotkey, netuid)); - let new_share_v2 = TotalHotkeySharesV2::::take(new_hotkey, netuid); - let total_new_shares = new_share_v1.add(&new_share_v2).unwrap_or_default(); - - TotalHotkeyShares::::remove(old_hotkey, netuid); - TotalHotkeyShares::::remove(new_hotkey, netuid); - - let total_old_plus_new_shares = - total_new_shares.add(&total_old_shares).unwrap_or_default(); - TotalHotkeySharesV2::::insert(new_hotkey, netuid, total_old_plus_new_shares); - weight.saturating_accrue(T::DbWeight::get().writes(3)); - } - // 3. Swap all subnet specific info. // 3.1 Remove the previous hotkey and insert the new hotkey from membership. @@ -565,21 +534,25 @@ impl Pallet { Self::transfer_root_claimable_for_new_hotkey(old_hotkey, new_hotkey); // 9.2. Insert the new alpha values. - for ((coldkey, netuid_alpha), alpha) in old_alpha_values { + // Iterate all coldkeys staking to old_hotkey, remove their stake from old_hotkey, + // and add to new_hotkey + for ((coldkey, netuid_alpha), _) in old_alpha_values { if netuid == netuid_alpha { Self::transfer_root_claimed_for_new_keys( netuid, old_hotkey, new_hotkey, &coldkey, &coldkey, ); - let new_alpha = Alpha::::take((new_hotkey, &coldkey, netuid)); - Alpha::::remove((old_hotkey, &coldkey, netuid)); - - // Insert into AlphaV2 because Alpha is deprecated - AlphaV2::::insert( - (new_hotkey, &coldkey, netuid), - SafeFloat::from(alpha.saturating_add(new_alpha)), + // Move stake from old hotkey to new hotkey + let alpha_old = Self::get_stake_for_hotkey_and_coldkey_on_subnet( + old_hotkey, &coldkey, netuid, ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + Self::decrease_stake_for_hotkey_and_coldkey_on_subnet( + old_hotkey, &coldkey, netuid, alpha_old, + ); + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + new_hotkey, &coldkey, netuid, alpha_old, + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); // Swap StakingHotkeys. // StakingHotkeys( coldkey ) --> Vec -- the hotkeys that the coldkey stakes. @@ -594,19 +567,23 @@ impl Pallet { } } - for ((coldkey, netuid_alpha), alpha) in old_alpha_values_v2 { + for ((coldkey, netuid_alpha), _) in old_alpha_values_v2 { if netuid == netuid_alpha { Self::transfer_root_claimed_for_new_keys( netuid, old_hotkey, new_hotkey, &coldkey, &coldkey, ); - let new_alpha_v2 = AlphaV2::::take((new_hotkey, &coldkey, netuid)); - AlphaV2::::remove((old_hotkey, &coldkey, netuid)); - AlphaV2::::insert( - (new_hotkey, &coldkey, netuid), - alpha.add(&new_alpha_v2).unwrap_or_default(), + // Move stake from old hotkey to new hotkey + let alpha_old = Self::get_stake_for_hotkey_and_coldkey_on_subnet( + old_hotkey, &coldkey, netuid, ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + Self::decrease_stake_for_hotkey_and_coldkey_on_subnet( + old_hotkey, &coldkey, netuid, alpha_old, + ); + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + new_hotkey, &coldkey, netuid, alpha_old, + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); // Swap StakingHotkeys. // StakingHotkeys( coldkey ) --> Vec -- the hotkeys that the coldkey stakes. diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index d0a1de3526..4c574ebba0 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -889,7 +889,7 @@ fn test_swap_stake_success() { let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let amount = 10_000; - let shares = U64F64::from_num(123456); + let shares = U64F64::from_num(10_000); let mut weight = Weight::zero(); // Initialize staking variables for old_hotkey @@ -974,7 +974,7 @@ fn test_swap_stake_v2_success() { let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let amount = 10_000; - let shares = U64F64::from_num(123456); + let shares = U64F64::from_num(10_000); let mut weight = Weight::zero(); // Initialize staking variables for old_hotkey @@ -1045,7 +1045,7 @@ fn test_swap_stake_v2_success() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_stake_old_hotkey_not_exist --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey::test_swap_stake_old_hotkey_not_exist --exact --nocapture #[test] fn test_swap_stake_old_hotkey_not_exist() { new_test_ext(1).execute_with(|| { @@ -1056,12 +1056,15 @@ fn test_swap_stake_old_hotkey_not_exist() { let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let alpha = AlphaBalance::from(1000); let alpha_share = U64F64::from_num(1234); let mut weight = Weight::zero(); let netuid = NetUid::from(1); // Initialize Stake for old_hotkey Alpha::::insert((old_hotkey, coldkey, netuid), alpha_share); + TotalHotkeyAlpha::::insert(old_hotkey, netuid, alpha); + TotalHotkeyShares::::insert(old_hotkey, netuid, alpha_share); // Ensure old_hotkey has a stake assert!(Alpha::::contains_key((old_hotkey, coldkey, netuid))); diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index fdcbadf408..93e6b6a95f 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -946,7 +946,7 @@ fn test_swap_stake_success() { let netuid = add_dynamic_network(&old_hotkey, &coldkey); SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); let amount = 10_000; - let shares = U64F64::from_num(123456); + let shares = U64F64::from_num(10_000); // Initialize staking variables for old_hotkey TotalHotkeyAlpha::::insert(old_hotkey, netuid, AlphaBalance::from(amount)); @@ -1033,7 +1033,7 @@ fn test_swap_stake_v2_success() { let netuid = add_dynamic_network(&old_hotkey, &coldkey); SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); let amount = 10_000; - let shares = U64F64::from_num(123456); + let shares = U64F64::from_num(10_000); // Initialize staking variables for old_hotkey TotalHotkeyAlpha::::insert(old_hotkey, netuid, AlphaBalance::from(amount)); @@ -2258,7 +2258,7 @@ fn test_revert_hotkey_swap_dividends() { SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); let amount = 10_000; - let shares = U64F64::from_num(123456); + let shares = U64F64::from_num(10_000); TotalHotkeyAlpha::::insert(hk1, netuid, AlphaBalance::from(amount)); TotalHotkeyAlphaLastEpoch::::insert(hk1, netuid, AlphaBalance::from(amount * 2)); @@ -2688,13 +2688,15 @@ fn test_swap_hotkey_with_existing_stake() { ); // Check total stake transfer - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), - 0.into() + 0.into(), + epsilon = 1.into() ); - assert_eq!( + assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), - total_hk1_stake + total_hk2_stake + total_hk1_stake + total_hk2_stake, + epsilon = 1.into() ); }); -} \ No newline at end of file +} From f7b8fbb68454ae356b6a2b567e1a8fc3c344e65b Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 25 Mar 2026 22:21:17 -0400 Subject: [PATCH 4/7] Optimize the stake swap in hk swap --- pallets/subtensor/src/swap/swap_hotkey.rs | 104 +++++++++------------- 1 file changed, 40 insertions(+), 64 deletions(-) diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index d3527e944f..aaf3f973c5 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -2,6 +2,7 @@ use super::*; use frame_support::weights::Weight; use share_pool::SafeFloat; use sp_core::Get; +use sp_std::collections::btree_set::BTreeSet; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{MechId, NetUid, Token}; @@ -518,7 +519,10 @@ impl Pallet { Self::swap_voting_power_for_hotkey(old_hotkey, new_hotkey, netuid); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - // 9. Swap Alpha + // Transfer root claimable + Self::transfer_root_claimable_for_new_hotkey(old_hotkey, new_hotkey); + + // Swap Alpha // Alpha( hotkey, coldkey, netuid ) -> alpha let old_alpha_values: Vec<((T::AccountId, NetUid), U64F64)> = Alpha::::iter_prefix((old_hotkey,)).collect(); @@ -530,71 +534,43 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(old_alpha_values_v2.len() as u64)); weight.saturating_accrue(T::DbWeight::get().writes(old_alpha_values_v2.len() as u64)); - // 9.1. Transfer root claimable - Self::transfer_root_claimable_for_new_hotkey(old_hotkey, new_hotkey); + // Insert the new alpha values. + // Deduplicate coldkeys staking to old_hotkey from alpha and alpha_v2 + let unique_coldkeys: BTreeSet = old_alpha_values + .into_iter() + .map(|((coldkey, netuid_alpha), _)| (coldkey, netuid_alpha)) + .chain( + old_alpha_values_v2 + .into_iter() + .map(|((coldkey, netuid_alpha), _)| (coldkey, netuid_alpha)), + ) + .filter(|(_, netuid_alpha)| *netuid_alpha == netuid) + .map(|(coldkey, _)| coldkey) + .collect(); + + // For each coldkey remove their stake from old_hotkey and add to new_hotkey + for coldkey in unique_coldkeys { + Self::transfer_root_claimed_for_new_keys( + netuid, old_hotkey, new_hotkey, &coldkey, &coldkey, + ); - // 9.2. Insert the new alpha values. - // Iterate all coldkeys staking to old_hotkey, remove their stake from old_hotkey, - // and add to new_hotkey - for ((coldkey, netuid_alpha), _) in old_alpha_values { - if netuid == netuid_alpha { - Self::transfer_root_claimed_for_new_keys( - netuid, old_hotkey, new_hotkey, &coldkey, &coldkey, - ); - - // Move stake from old hotkey to new hotkey - let alpha_old = Self::get_stake_for_hotkey_and_coldkey_on_subnet( - old_hotkey, &coldkey, netuid, - ); - Self::decrease_stake_for_hotkey_and_coldkey_on_subnet( - old_hotkey, &coldkey, netuid, alpha_old, - ); - Self::increase_stake_for_hotkey_and_coldkey_on_subnet( - new_hotkey, &coldkey, netuid, alpha_old, - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - - // Swap StakingHotkeys. - // StakingHotkeys( coldkey ) --> Vec -- the hotkeys that the coldkey stakes. - let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); - weight.saturating_accrue(T::DbWeight::get().reads(1)); - if staking_hotkeys.contains(old_hotkey) && !staking_hotkeys.contains(new_hotkey) - { - staking_hotkeys.push(new_hotkey.clone()); - StakingHotkeys::::insert(&coldkey, staking_hotkeys); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - } - } + let alpha_old = + Self::get_stake_for_hotkey_and_coldkey_on_subnet(old_hotkey, &coldkey, netuid); + Self::decrease_stake_for_hotkey_and_coldkey_on_subnet( + old_hotkey, &coldkey, netuid, alpha_old, + ); + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( + new_hotkey, &coldkey, netuid, alpha_old, + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - for ((coldkey, netuid_alpha), _) in old_alpha_values_v2 { - if netuid == netuid_alpha { - Self::transfer_root_claimed_for_new_keys( - netuid, old_hotkey, new_hotkey, &coldkey, &coldkey, - ); - - // Move stake from old hotkey to new hotkey - let alpha_old = Self::get_stake_for_hotkey_and_coldkey_on_subnet( - old_hotkey, &coldkey, netuid, - ); - Self::decrease_stake_for_hotkey_and_coldkey_on_subnet( - old_hotkey, &coldkey, netuid, alpha_old, - ); - Self::increase_stake_for_hotkey_and_coldkey_on_subnet( - new_hotkey, &coldkey, netuid, alpha_old, - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - - // Swap StakingHotkeys. - // StakingHotkeys( coldkey ) --> Vec -- the hotkeys that the coldkey stakes. - let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); - weight.saturating_accrue(T::DbWeight::get().reads(1)); - if staking_hotkeys.contains(old_hotkey) && !staking_hotkeys.contains(new_hotkey) - { - staking_hotkeys.push(new_hotkey.clone()); - StakingHotkeys::::insert(&coldkey, staking_hotkeys); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } + let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + if staking_hotkeys.contains(old_hotkey) && !staking_hotkeys.contains(new_hotkey) { + staking_hotkeys.push(new_hotkey.clone()); + StakingHotkeys::::insert(&coldkey, staking_hotkeys); + weight.saturating_accrue(T::DbWeight::get().writes(1)); } } } From e7f8151dcfbb2b2e53bb7a8d42933f00f862ad1f Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Thu, 26 Mar 2026 09:38:36 -0400 Subject: [PATCH 5/7] Patch 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 f70b83f52d..0ab7f3081e 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1063,9 +1063,9 @@ mod dispatches { note = "Please use swap_hotkey_v2 instead. This extrinsic will be removed some time after June 2026." )] #[pallet::call_index(70)] - #[pallet::weight((Weight::from_parts(275_300_000, 0) - .saturating_add(T::DbWeight::get().reads(57_u64)) - .saturating_add(T::DbWeight::get().writes(39_u64)), DispatchClass::Normal, Pays::No))] + #[pallet::weight((Weight::from_parts(263_300_000, 0) + .saturating_add(T::DbWeight::get().reads(45_u64)) + .saturating_add(T::DbWeight::get().writes(31_u64)), DispatchClass::Normal, Pays::No))] pub fn swap_hotkey( origin: OriginFor, hotkey: T::AccountId, From bf3cda2b42ab1bb16bde1fd927eec40547abb72b Mon Sep 17 00:00:00 2001 From: Evgeny Svirsky Date: Thu, 26 Mar 2026 18:34:35 +0100 Subject: [PATCH 6/7] Added more test for revert hotfix --- .../src/tests/swap_hotkey_with_subnet.rs | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index 93e6b6a95f..44462ab358 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -2700,3 +2700,193 @@ fn test_swap_hotkey_with_existing_stake() { ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_hotkey_swap_with_revert_stake_the_same --exact --nocapture +#[test] +fn test_revert_hotkey_swap_with_revert_stake_the_same() { + new_test_ext(1).execute_with(|| { + let netuid_1 = NetUid::from(1); + let netuid_2 = NetUid::from(2); + let tempo: u16 = 13; + let hk1 = U256::from(1); + let new_hotkey = U256::from(2); + let random_hotkey = U256::from(3); + let coldkey = U256::from(3); + let coldkey_2 = U256::from(4); + let coldkey_3 = U256::from(5); + let coldkey_4 = U256::from(6); + let random_coldkey = U256::from(7); + let initial_balance = 10_000_000_000u64 * 2; + let stake1 = 500_000_000u64; + let stake2 = 1_000_000_000u64; + let stake_ck2 = 1_500_000_000u64; + let stake_ck3 = 300_000_000u64; + let stake_ck4 = 900_000_000u64; + + assert_ok!(SubtensorModule::try_associate_hotkey( + <::RuntimeOrigin>::signed(random_coldkey), + random_hotkey + )); + + // Setup + super::mock::setup_reserves( + netuid_1, + (stake_ck4 * 100).into(), + (stake_ck4 * 100).into(), + ); + super::mock::setup_reserves( + netuid_2, + (stake_ck4 * 100).into(), + (stake_ck4 * 100).into(), + ); + + add_network(netuid_1, tempo, 0); + add_network(netuid_2, tempo, 0); + + SubnetMechanism::::insert(netuid_1, 1); + SubnetMechanism::::insert(netuid_2, 1); + + register_ok_neuron(netuid_1, hk1, coldkey, 0); + register_ok_neuron(netuid_2, hk1, coldkey, 0); + + SubtensorModule::add_balance_to_coldkey_account(&coldkey, initial_balance.into()); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_4, initial_balance.into()); + SubtensorModule::add_balance_to_coldkey_account(&random_coldkey, initial_balance.into()); + step_block(20); // Waiting interval to be able to swap later + + // Checking stake for hk1 on both networks + let hk1_stake_before_increase_sn_1 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid_1); + assert!( + hk1_stake_before_increase_sn_1 == 0.into(), + "hk1 should have empty stake" + ); + + let hk1_stake_before_increase_sn_2 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid_2); + assert!( + hk1_stake_before_increase_sn_2 == 0.into(), + "hk1 should have empty stake" + ); + + // Adding stake to hk1 on both networks + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hk1, + &coldkey, + netuid_1, + stake1.into(), + ); + // Adding another stake for different coldkey + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hk1, + &coldkey_2, + netuid_1, + stake_ck2.into(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hk1, + &coldkey_3, + netuid_1, + stake_ck3.into(), + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hk1, + &coldkey, + netuid_2, + stake2.into(), + ); + + // The stake for validator + let hk1_stake_before_swap_sn_1 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid_1); + assert!( + hk1_stake_before_swap_sn_1 == stake1.into(), + "hk1 should have stake before swap on sn_1" + ); + + // Let's check individual stake + let hk1_stake_before_swap_sn_1 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey_2, netuid_1); + assert_eq!( + hk1_stake_before_swap_sn_1, (stake_ck2).into(), + "stake for ck2 should be only his stake" + ); + + let hk1_stake_before_swap_sn_2 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid_2); + assert!( + hk1_stake_before_swap_sn_2 == stake2.into(), + "hk1 should have stake before swap on sn_2" + ); + + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &hk1, + &new_hotkey, + Some(netuid_1), + false + )); + + assert_eq!(Owner::::get(hk1), coldkey); + + SubtensorModule::do_add_stake( + RawOrigin::Signed(random_coldkey).into(), + hk1, + netuid_1, + stake_ck4.into(), + ) + .unwrap(); + + // Check stake moved to new hotkey on subnet1 + let new_hotkey_stake_after_swap_ck = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey, netuid_1); + assert_eq!(new_hotkey_stake_after_swap_ck, stake1.into()); + + // Check stake moved for ck2 + let new_hotkey_stake_after_swap_ck_1 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey_2, netuid_1); + assert_eq!(new_hotkey_stake_after_swap_ck_1, stake_ck2.into()); + + // Check stake moved for ck3 + let new_hotkey_stake_after_swap_ck_3 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey_3, netuid_1); + assert_eq!(new_hotkey_stake_after_swap_ck_3, stake_ck3.into()); + + step_block(20); + + // Let's check individual stakes; they changed because of emissions + let new_hotkey_stake_before_revert_ck = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey, netuid_1); + assert!(new_hotkey_stake_before_revert_ck > stake1.into()); + + let new_hotkey_stake_before_revert_ck_2 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey_2, netuid_1); + assert!(new_hotkey_stake_before_revert_ck_2 > stake_ck2.into()); + + let new_hotkey_stake_before_revert_ck_3 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey_3, netuid_1); + assert!(new_hotkey_stake_before_revert_ck_3 > stake_ck3.into()); + + // Reverting back: hk2 -> hk1 + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &new_hotkey, + &hk1, + Some(netuid_1), + false + )); + + // Let's check individual stakes; they changed because of emissions + let old_hotkey_stake_after_revert_ck = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid_1); + assert_eq!(old_hotkey_stake_after_revert_ck, new_hotkey_stake_before_revert_ck); + + let old_hotkey_stake_after_revert_ck_2 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey_2, netuid_1); + assert_eq!(old_hotkey_stake_after_revert_ck_2, new_hotkey_stake_before_revert_ck_2); + + let old_hotkey_stake_after_revert_ck_3 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey_3, netuid_1); + assert_eq!(old_hotkey_stake_after_revert_ck_3, new_hotkey_stake_before_revert_ck_3); + }); +} \ No newline at end of file From d7e68f9f68cad114d8657d46c790d19bce15b510 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Thu, 26 Mar 2026 15:14:08 -0400 Subject: [PATCH 7/7] fmt --- .../src/tests/swap_hotkey_with_subnet.rs | 70 +++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index 44462ab358..5214b1a5f2 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -2729,16 +2729,8 @@ fn test_revert_hotkey_swap_with_revert_stake_the_same() { )); // Setup - super::mock::setup_reserves( - netuid_1, - (stake_ck4 * 100).into(), - (stake_ck4 * 100).into(), - ); - super::mock::setup_reserves( - netuid_2, - (stake_ck4 * 100).into(), - (stake_ck4 * 100).into(), - ); + super::mock::setup_reserves(netuid_1, (stake_ck4 * 100).into(), (stake_ck4 * 100).into()); + super::mock::setup_reserves(netuid_2, (stake_ck4 * 100).into(), (stake_ck4 * 100).into()); add_network(netuid_1, tempo, 0); add_network(netuid_2, tempo, 0); @@ -2808,7 +2800,8 @@ fn test_revert_hotkey_swap_with_revert_stake_the_same() { let hk1_stake_before_swap_sn_1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey_2, netuid_1); assert_eq!( - hk1_stake_before_swap_sn_1, (stake_ck2).into(), + hk1_stake_before_swap_sn_1, + (stake_ck2).into(), "stake for ck2 should be only his stake" ); @@ -2835,36 +2828,60 @@ fn test_revert_hotkey_swap_with_revert_stake_the_same() { netuid_1, stake_ck4.into(), ) - .unwrap(); + .unwrap(); // Check stake moved to new hotkey on subnet1 let new_hotkey_stake_after_swap_ck = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey, netuid_1); + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &coldkey, + netuid_1, + ); assert_eq!(new_hotkey_stake_after_swap_ck, stake1.into()); // Check stake moved for ck2 let new_hotkey_stake_after_swap_ck_1 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey_2, netuid_1); + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &coldkey_2, + netuid_1, + ); assert_eq!(new_hotkey_stake_after_swap_ck_1, stake_ck2.into()); // Check stake moved for ck3 let new_hotkey_stake_after_swap_ck_3 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey_3, netuid_1); + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &coldkey_3, + netuid_1, + ); assert_eq!(new_hotkey_stake_after_swap_ck_3, stake_ck3.into()); step_block(20); // Let's check individual stakes; they changed because of emissions let new_hotkey_stake_before_revert_ck = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey, netuid_1); + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &coldkey, + netuid_1, + ); assert!(new_hotkey_stake_before_revert_ck > stake1.into()); let new_hotkey_stake_before_revert_ck_2 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey_2, netuid_1); + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &coldkey_2, + netuid_1, + ); assert!(new_hotkey_stake_before_revert_ck_2 > stake_ck2.into()); let new_hotkey_stake_before_revert_ck_3 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&new_hotkey, &coldkey_3, netuid_1); + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &coldkey_3, + netuid_1, + ); assert!(new_hotkey_stake_before_revert_ck_3 > stake_ck3.into()); // Reverting back: hk2 -> hk1 @@ -2879,14 +2896,23 @@ fn test_revert_hotkey_swap_with_revert_stake_the_same() { // Let's check individual stakes; they changed because of emissions let old_hotkey_stake_after_revert_ck = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid_1); - assert_eq!(old_hotkey_stake_after_revert_ck, new_hotkey_stake_before_revert_ck); + assert_eq!( + old_hotkey_stake_after_revert_ck, + new_hotkey_stake_before_revert_ck + ); let old_hotkey_stake_after_revert_ck_2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey_2, netuid_1); - assert_eq!(old_hotkey_stake_after_revert_ck_2, new_hotkey_stake_before_revert_ck_2); + assert_eq!( + old_hotkey_stake_after_revert_ck_2, + new_hotkey_stake_before_revert_ck_2 + ); let old_hotkey_stake_after_revert_ck_3 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey_3, netuid_1); - assert_eq!(old_hotkey_stake_after_revert_ck_3, new_hotkey_stake_before_revert_ck_3); + assert_eq!( + old_hotkey_stake_after_revert_ck_3, + new_hotkey_stake_before_revert_ck_3 + ); }); -} \ No newline at end of file +}