diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 93803cfc13..19a8f635dc 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1051,7 +1051,17 @@ mod dispatches { Self::do_burned_registration(origin, netuid, hotkey) } - /// The extrinsic for user to change its hotkey in subnet or all subnets. + /// ---- The extrinsic for user to change its hotkey in subnet or all subnets. + /// + /// # Arguments + /// * `origin` - The origin of the transaction (must be signed by the coldkey). + /// * `hotkey` - The old hotkey to be swapped. + /// * `new_hotkey` - The new hotkey to replace the old one. + /// * `netuid` - Optional subnet ID. If `Some`, swap only on that subnet; if `None`, swap on all subnets. + /// is transferred to the new hotkey. + #[deprecated( + 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(52_u64)) @@ -1062,7 +1072,32 @@ mod dispatches { new_hotkey: T::AccountId, netuid: Option, ) -> DispatchResultWithPostInfo { - Self::do_swap_hotkey(origin, &hotkey, &new_hotkey, netuid) + Self::do_swap_hotkey(origin, &hotkey, &new_hotkey, netuid, false) + } + + /// ---- The extrinsic for user to change its hotkey in subnet or all subnets. This extrinsic is + /// similar to swap_hotkey, but with keep_stake parameter bo be able to keep the stake when swapping + /// a root key to a child key + /// + /// # Arguments + /// * `origin` - The origin of the transaction (must be signed by the coldkey). + /// * `hotkey` - The old hotkey to be swapped. + /// * `new_hotkey` - The new hotkey to replace the old one. + /// * `netuid` - Optional subnet ID. If `Some`, swap only on that subnet; if `None`, swap on all subnets. + /// * `keep_stake` - If `true`, stake remains on the old hotkey and the rest metadata + /// is transferred to the new hotkey. + #[pallet::call_index(72)] + #[pallet::weight((Weight::from_parts(275_300_000, 0) + .saturating_add(T::DbWeight::get().reads(52_u64)) + .saturating_add(T::DbWeight::get().writes(35_u64)), DispatchClass::Normal, Pays::No))] + pub fn swap_hotkey_v2( + origin: OriginFor, + hotkey: T::AccountId, + new_hotkey: T::AccountId, + netuid: Option, + keep_stake: bool, + ) -> DispatchResultWithPostInfo { + Self::do_swap_hotkey(origin, &hotkey, &new_hotkey, netuid, keep_stake) } /// Performs an arbitrary coldkey swap for any coldkey. diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 1652610afb..488778e33e 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -13,6 +13,7 @@ impl Pallet { /// * `old_hotkey` - The old hotkey to be swapped. /// * `new_hotkey` - The new hotkey to replace the old one. /// * `netuid` - The hotkey swap in a subnet or all subnets. + /// * `keep_stake` - If `true`, stake remains on the old hotkey and the rest metadata /// /// # Returns /// @@ -30,6 +31,7 @@ impl Pallet { old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, netuid: Option, + keep_stake: bool, ) -> DispatchResultWithPostInfo { // 1. Ensure the origin is signed and get the coldkey let coldkey = ensure_signed(origin)?; @@ -57,11 +59,22 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(2)); - // 7. Ensure the new hotkey is not already registered on any network - ensure!( - !Self::is_hotkey_registered_on_any_network(new_hotkey), - Error::::HotKeyAlreadyRegisteredInSubNet - ); + match netuid { + // 7. Ensure the hotkey is not registered on the network before, if netuid is provided + Some(netuid) => { + ensure!( + !Self::is_hotkey_registered_on_specific_network(new_hotkey, netuid), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + } + // 7.1 Ensure the new hotkey is not already registered on any network, only if netuid is none + None => { + ensure!( + !Self::is_hotkey_registered_on_any_network(new_hotkey), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + } + } // 8. Swap LastTxBlock let last_tx_block: u64 = Self::get_last_tx_block(old_hotkey); @@ -80,7 +93,9 @@ impl Pallet { // 11. fork for swap hotkey on a specific subnet case after do the common check if let Some(netuid) = netuid { - return Self::swap_hotkey_on_subnet(&coldkey, old_hotkey, new_hotkey, netuid, weight); + return Self::swap_hotkey_on_subnet( + &coldkey, old_hotkey, new_hotkey, netuid, weight, keep_stake, + ); }; // Start to do everything for swap hotkey on all subnets case @@ -105,7 +120,13 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); // 19. Perform the hotkey swap - Self::perform_hotkey_swap_on_all_subnets(old_hotkey, new_hotkey, &coldkey, &mut weight)?; + Self::perform_hotkey_swap_on_all_subnets( + old_hotkey, + new_hotkey, + &coldkey, + &mut weight, + keep_stake, + )?; // 20. Update the last transaction block for the coldkey Self::set_last_tx_block(&coldkey, block); @@ -160,6 +181,7 @@ impl Pallet { new_hotkey: &T::AccountId, coldkey: &T::AccountId, weight: &mut Weight, + keep_stake: bool, ) -> DispatchResult { // 1. keep the old hotkey alpha values for the case where hotkey staked by multiple coldkeys. let old_alpha_values: Vec<((T::AccountId, NetUid), U64F64)> = @@ -188,7 +210,9 @@ impl Pallet { // 5. execute the hotkey swap on all subnets for netuid in Self::get_all_subnet_netuids() { - Self::perform_hotkey_swap_on_one_subnet(old_hotkey, new_hotkey, weight, netuid)?; + Self::perform_hotkey_swap_on_one_subnet( + old_hotkey, new_hotkey, weight, netuid, keep_stake, + )?; } // 6. Swap LastTxBlock @@ -217,18 +241,20 @@ impl Pallet { // 10. Alpha already update in perform_hotkey_swap_on_one_subnet // Update the StakingHotkeys for the case where hotkey staked by multiple coldkeys. - for ((coldkey, _netuid), _alpha) in old_alpha_values { - // 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.retain(|hk| *hk != *old_hotkey && *hk != *new_hotkey); - if !staking_hotkeys.contains(new_hotkey) { - staking_hotkeys.push(new_hotkey.clone()); + if !keep_stake { + for ((coldkey, _netuid), _alpha) in old_alpha_values { + // 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.retain(|hk| *hk != *old_hotkey && *hk != *new_hotkey); + if !staking_hotkeys.contains(new_hotkey) { + staking_hotkeys.push(new_hotkey.clone()); + } + StakingHotkeys::::insert(&coldkey, staking_hotkeys); + weight.saturating_accrue(T::DbWeight::get().writes(1)); } - StakingHotkeys::::insert(&coldkey, staking_hotkeys); - weight.saturating_accrue(T::DbWeight::get().writes(1)); } } @@ -242,6 +268,7 @@ impl Pallet { new_hotkey: &T::AccountId, netuid: NetUid, init_weight: Weight, + keep_stake: bool, ) -> DispatchResultWithPostInfo { // 1. Ensure coldkey not swap hotkey too frequently let mut weight: Weight = init_weight; @@ -299,7 +326,13 @@ impl Pallet { } // 9. Perform the hotkey swap - Self::perform_hotkey_swap_on_one_subnet(old_hotkey, new_hotkey, &mut weight, netuid)?; + Self::perform_hotkey_swap_on_one_subnet( + old_hotkey, + new_hotkey, + &mut weight, + netuid, + keep_stake, + )?; // 10. Update the last transaction block for the coldkey Self::set_last_tx_block(coldkey, block); @@ -323,24 +356,27 @@ impl Pallet { new_hotkey: &T::AccountId, weight: &mut Weight, netuid: NetUid, + keep_stake: bool, ) -> DispatchResult { // 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); + // Only transfer stake when keep_stake is false. + if !keep_stake { + 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)); + 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 ) -> alpha -- the total alpha that the hotkey has on a specific subnet. - let share = TotalHotkeyShares::::take(old_hotkey, netuid); - // TotalHotkeyAlpha::::remove(old_hotkey, netuid); - TotalHotkeyShares::::mutate(new_hotkey, netuid, |value| { - *value = value.saturating_add(share) - }); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + // 2. Swap total hotkey shares on all subnets it exists on. + // TotalHotkeyShares( hotkey, netuid ) -> alpha -- the total alpha that the hotkey has on a specific subnet. + let share = TotalHotkeyShares::::take(old_hotkey, netuid); + TotalHotkeyShares::::mutate(new_hotkey, netuid, |value| { + *value = value.saturating_add(share) + }); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + } // 3. Swap all subnet specific info. @@ -424,6 +460,7 @@ impl Pallet { NeuronCertificates::::insert(netuid, new_hotkey, old_neuron_certificates); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } + // 4. Swap ChildKeys. // 5. Swap ParentKeys. // 6. Swap PendingChildKeys. @@ -469,69 +506,71 @@ impl Pallet { } // 8. Swap dividend records - // 8.1 Swap TotalHotkeyAlphaLastEpoch - let old_alpha = TotalHotkeyAlphaLastEpoch::::take(old_hotkey, netuid); - let new_total_hotkey_alpha = TotalHotkeyAlphaLastEpoch::::get(new_hotkey, netuid); - TotalHotkeyAlphaLastEpoch::::insert( - new_hotkey, - netuid, - old_alpha.saturating_add(new_total_hotkey_alpha), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - - // 8.2 Swap AlphaDividendsPerSubnet - let old_hotkey_alpha_dividends = AlphaDividendsPerSubnet::::get(netuid, old_hotkey); - let new_hotkey_alpha_dividends = AlphaDividendsPerSubnet::::get(netuid, new_hotkey); - AlphaDividendsPerSubnet::::remove(netuid, old_hotkey); - AlphaDividendsPerSubnet::::insert( - netuid, - new_hotkey, - old_hotkey_alpha_dividends.saturating_add(new_hotkey_alpha_dividends), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - - // 8.3 Swap TaoDividendsPerSubnet - // Tao dividends were removed - - // 8.4 Swap VotingPower - // VotingPower( netuid, hotkey ) --> u64 -- the voting power EMA for the hotkey. - Self::swap_voting_power_for_hotkey(old_hotkey, new_hotkey, netuid); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - - // 9. Swap Alpha - // Alpha( hotkey, coldkey, netuid ) -> alpha - let old_alpha_values: Vec<((T::AccountId, NetUid), U64F64)> = - Alpha::::iter_prefix((old_hotkey,)).collect(); - weight.saturating_accrue(T::DbWeight::get().reads(old_alpha_values.len() as u64)); - weight.saturating_accrue(T::DbWeight::get().writes(old_alpha_values.len() as u64)); + if !keep_stake { + // 8.1 Swap TotalHotkeyAlphaLastEpoch + let old_alpha = TotalHotkeyAlphaLastEpoch::::take(old_hotkey, netuid); + let new_total_hotkey_alpha = TotalHotkeyAlphaLastEpoch::::get(new_hotkey, netuid); + TotalHotkeyAlphaLastEpoch::::insert( + new_hotkey, + netuid, + old_alpha.saturating_add(new_total_hotkey_alpha), + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - // 9.1. Transfer root claimable + // 8.2 Swap AlphaDividendsPerSubnet + let old_hotkey_alpha_dividends = AlphaDividendsPerSubnet::::get(netuid, old_hotkey); + let new_hotkey_alpha_dividends = AlphaDividendsPerSubnet::::get(netuid, new_hotkey); + AlphaDividendsPerSubnet::::remove(netuid, old_hotkey); + AlphaDividendsPerSubnet::::insert( + netuid, + new_hotkey, + old_hotkey_alpha_dividends.saturating_add(new_hotkey_alpha_dividends), + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - Self::transfer_root_claimable_for_new_hotkey(old_hotkey, new_hotkey); + // 8.3 Swap TaoDividendsPerSubnet + // Tao dividends were removed - // 9.2. Insert the new alpha values. - for ((coldkey, netuid_alpha), alpha) in old_alpha_values { - if netuid == netuid_alpha { - Self::transfer_root_claimed_for_new_keys( - netuid, old_hotkey, new_hotkey, &coldkey, &coldkey, - ); + // 8.4 Swap VotingPower + // VotingPower( netuid, hotkey ) --> u64 -- the voting power EMA for the hotkey. + Self::swap_voting_power_for_hotkey(old_hotkey, new_hotkey, netuid); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - let new_alpha = Alpha::::take((new_hotkey, &coldkey, netuid)); - Alpha::::remove((old_hotkey, &coldkey, netuid)); - Alpha::::insert( - (new_hotkey, &coldkey, netuid), - alpha.saturating_add(new_alpha), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + // 9. Swap Alpha + // Alpha( hotkey, coldkey, netuid ) -> alpha + let old_alpha_values: Vec<((T::AccountId, NetUid), U64F64)> = + Alpha::::iter_prefix((old_hotkey,)).collect(); + weight.saturating_accrue(T::DbWeight::get().reads(old_alpha_values.len() as u64)); + weight.saturating_accrue(T::DbWeight::get().writes(old_alpha_values.len() as u64)); + + // 9.1. Transfer root claimable + 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 { + 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)); + Alpha::::insert( + (new_hotkey, &coldkey, netuid), + alpha.saturating_add(new_alpha), + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 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)); + // 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)); + } } } } diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 70999ebf01..0f628d6b86 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1280,7 +1280,8 @@ fn test_claim_root_with_swap_hotkey() { &hotkey, &new_hotkey, &mut weight, - netuid + netuid, + false, )); // Check swapped keys claimed values diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index 87812a32aa..d7f5a1274f 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -29,7 +29,8 @@ fn test_swap_owner() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); assert!(!Owner::::contains_key(old_hotkey)); @@ -51,7 +52,8 @@ fn test_swap_owned_hotkeys() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); let hotkeys = OwnedHotkeys::::get(coldkey); @@ -105,7 +107,8 @@ fn test_swap_total_hotkey_stake() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); // Verify that total hotkey stake swapped @@ -135,7 +138,8 @@ fn test_swap_delegates() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); assert!(!Delegates::::contains_key(old_hotkey)); @@ -159,7 +163,8 @@ fn test_swap_subnet_membership() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); @@ -187,7 +192,8 @@ fn test_swap_uids_and_keys() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); assert_eq!(Uids::::get(netuid, old_hotkey), None); @@ -215,7 +221,8 @@ fn test_swap_prometheus() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); assert!(!Prometheus::::contains_key(netuid, old_hotkey)); @@ -245,7 +252,8 @@ fn test_swap_axons() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); assert!(!Axons::::contains_key(netuid, old_hotkey)); @@ -272,7 +280,8 @@ fn test_swap_certificates() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); assert!(!NeuronCertificates::::contains_key( @@ -309,7 +318,8 @@ fn test_swap_weight_commits() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); assert!(!WeightCommits::::contains_key( @@ -346,7 +356,8 @@ fn test_swap_loaded_emission() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); let new_loaded_emission = LoadedEmission::::get(netuid); @@ -379,7 +390,8 @@ fn test_swap_staking_hotkeys() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); let staking_hotkeys = StakingHotkeys::::get(coldkey); @@ -434,7 +446,8 @@ fn test_swap_hotkey_with_multiple_coldkeys() { &old_hotkey, &new_hotkey, &coldkey1, - &mut weight + &mut weight, + false )); assert_eq!( @@ -470,7 +483,8 @@ fn test_swap_hotkey_with_multiple_subnets() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); assert!(IsNetworkMember::::get(new_hotkey, netuid1)); @@ -528,7 +542,8 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { &old_hotkey, &new_hotkey, &coldkey1, - &mut weight + &mut weight, + false )); // Check if new_hotkey replaced old_hotkey in StakingHotkeys @@ -563,7 +578,8 @@ fn test_swap_hotkey_with_no_stake() { &old_hotkey, &new_hotkey, &coldkey, - &mut weight + &mut weight, + false )); // Check if ownership transferred @@ -638,7 +654,8 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { &old_hotkey, &new_hotkey, &coldkey1, - &mut weight + &mut weight, + false )); // Check ownership transfer @@ -745,7 +762,8 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { <::RuntimeOrigin>::signed(coldkey), &old_hotkey, &new_hotkey_1, - None + None, + false, )); // Attempt to perform another swap immediately, which should fail due to rate limit @@ -754,7 +772,8 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { <::RuntimeOrigin>::signed(coldkey), &new_hotkey_1, &new_hotkey_2, - None + None, + false, ), Error::::HotKeySetTxRateLimitExceeded ); @@ -765,7 +784,8 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { <::RuntimeOrigin>::signed(coldkey), &new_hotkey_1, &new_hotkey_2, - None + None, + false, )); }); } @@ -793,7 +813,8 @@ fn test_do_swap_hotkey_err_not_owner() { <::RuntimeOrigin>::signed(not_owner_coldkey), &old_hotkey, &new_hotkey, - None + None, + false, ), Error::::NonAssociatedColdKey ); @@ -818,6 +839,7 @@ fn test_swap_owner_old_hotkey_not_exist() { &new_hotkey, &coldkey, &mut weight, + false, ); // Verify the swap @@ -846,6 +868,7 @@ fn test_swap_owner_new_hotkey_already_exists() { &new_hotkey, &coldkey, &mut weight, + false, ); // Verify the swap @@ -885,6 +908,7 @@ fn test_swap_stake_success() { &new_hotkey, &coldkey, &mut weight, + false, ); // Verify the swap @@ -958,6 +982,7 @@ fn test_swap_stake_old_hotkey_not_exist() { &new_hotkey, &coldkey, &mut weight, + false, ); // Verify that new_hotkey has the stake and old_hotkey does not @@ -1014,7 +1039,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - None + None, + false, ), Error::::NotEnoughBalanceToPaySwapHotKey ); @@ -1028,7 +1054,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(coldkey), &old_hotkey, &old_hotkey, - None + None, + false, ), Error::::NewHotKeyIsSameWithOld ); @@ -1040,7 +1067,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - None + None, + false, ), Error::::HotKeyAlreadyRegisteredInSubNet ); @@ -1052,7 +1080,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(wrong_coldkey), &old_hotkey, &new_hotkey, - None + None, + false, ), Error::::NonAssociatedColdKey ); @@ -1062,7 +1091,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - None + None, + false, )); // Check balance after swap @@ -1091,6 +1121,7 @@ fn test_swap_child_keys() { &new_hotkey, &coldkey, &mut weight, + false, ); // Verify the swap @@ -1124,6 +1155,7 @@ fn test_swap_parent_keys() { &new_hotkey, &coldkey, &mut weight, + false, ); // Verify ParentKeys swap @@ -1168,6 +1200,7 @@ fn test_swap_multiple_subnets() { &new_hotkey, &coldkey, &mut weight, + false, ); // Verify the swap for both subnets @@ -1218,6 +1251,7 @@ fn test_swap_complex_parent_child_structure() { &new_hotkey, &coldkey, &mut weight, + false, ); // Verify ParentKeys swap @@ -1278,7 +1312,8 @@ fn test_swap_parent_hotkey_childkey_maps() { &parent_old, &parent_new, &coldkey, - &mut weight + &mut weight, + false )); // Verify parent and child keys updates @@ -1333,7 +1368,8 @@ fn test_swap_child_hotkey_childkey_maps() { &child_old, &child_new, &coldkey, - &mut weight + &mut weight, + false )); // Verify parent and child keys updates @@ -1372,6 +1408,7 @@ fn test_swap_hotkey_is_sn_owner_hotkey() { &new_hotkey, &coldkey, &mut weight, + false, ); // Check for SubnetOwnerHotkey @@ -1405,7 +1442,8 @@ fn test_swap_hotkey_swap_rate_limits() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - None + None, + false, )); // Check for new hotkey @@ -1463,7 +1501,8 @@ fn test_swap_parent_hotkey_self_loops_in_pending() { &parent_old, &parent_new, &coldkey, - &mut weight + &mut weight, + false ), Error::::InvalidChild ); @@ -1491,6 +1530,7 @@ fn test_swap_auto_stake_destination_coldkeys() { &new_hotkey, &coldkey, &mut weight, + false, ); // Verify the swap diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index f71133859c..6f43d46fde 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -11,7 +11,9 @@ use super::mock::*; use crate::*; use sp_core::{Get, H160, H256, U256}; use sp_runtime::SaturatedConversion; +use std::collections::BTreeSet; use substrate_fixed::types::U64F64; + // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_owner --exact --nocapture #[test] fn test_swap_owner() { @@ -28,7 +30,8 @@ fn test_swap_owner() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false, )); assert_eq!(Owner::::get(old_hotkey), coldkey); @@ -53,7 +56,8 @@ fn test_swap_owned_hotkeys() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); let hotkeys = OwnedHotkeys::::get(coldkey); @@ -105,7 +109,8 @@ fn test_swap_total_hotkey_stake() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); // Verify that total hotkey stake swapped @@ -139,7 +144,8 @@ fn test_swap_delegates() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert!(Delegates::::contains_key(old_hotkey)); @@ -164,7 +170,8 @@ fn test_swap_subnet_membership() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); @@ -193,7 +200,8 @@ fn test_swap_uids_and_keys() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert_eq!(Uids::::get(netuid, old_hotkey), None); @@ -224,7 +232,8 @@ fn test_swap_prometheus() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert!(!Prometheus::::contains_key(netuid, old_hotkey)); @@ -257,7 +266,8 @@ fn test_swap_axons() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert!(!Axons::::contains_key(netuid, old_hotkey)); @@ -287,7 +297,8 @@ fn test_swap_certificates() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert!(!NeuronCertificates::::contains_key( @@ -327,7 +338,8 @@ fn test_swap_weight_commits() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert!(!WeightCommits::::contains_key( @@ -367,7 +379,8 @@ fn test_swap_loaded_emission() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); let new_loaded_emission = LoadedEmission::::get(netuid); @@ -396,7 +409,8 @@ fn test_swap_staking_hotkeys() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); let staking_hotkeys = StakingHotkeys::::get(coldkey); @@ -446,7 +460,8 @@ fn test_swap_hotkey_with_multiple_coldkeys() { RuntimeOrigin::signed(coldkey1), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert_eq!( @@ -494,7 +509,8 @@ fn test_swap_hotkey_with_multiple_subnets() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid1) + Some(netuid1), + false )); System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); @@ -502,7 +518,8 @@ fn test_swap_hotkey_with_multiple_subnets() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey_2, - Some(netuid2) + Some(netuid2), + false )); assert!(IsNetworkMember::::get(new_hotkey, netuid1)); @@ -554,7 +571,8 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { RuntimeOrigin::signed(coldkey1), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); // Check if new_hotkey replaced old_hotkey in StakingHotkeys @@ -591,7 +609,8 @@ fn test_swap_hotkey_with_no_stake() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); // Check if ownership transferred @@ -663,7 +682,8 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { RuntimeOrigin::signed(coldkey1), &old_hotkey, &new_hotkey, - Some(netuid1) + Some(netuid1), + false )); System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); @@ -671,7 +691,8 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { RuntimeOrigin::signed(coldkey1), &old_hotkey, &new_hotkey_2, - Some(netuid2) + Some(netuid2), + false )); // Check ownership transfer @@ -785,7 +806,8 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey_1, - Some(netuid) + Some(netuid), + false ),); // Attempt to perform another swap immediately, which should fail due to rate limit @@ -794,7 +816,8 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey_1, - Some(netuid) + Some(netuid), + false ), Error::::HotKeySetTxRateLimitExceeded ); @@ -806,7 +829,8 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { <::RuntimeOrigin>::signed(coldkey), &new_hotkey_1, &new_hotkey_2, - None + None, + false )); }); } @@ -834,7 +858,8 @@ fn test_do_swap_hotkey_err_not_owner() { RuntimeOrigin::signed(not_owner_coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ), Error::::NonAssociatedColdKey ); @@ -861,7 +886,8 @@ fn test_swap_owner_old_hotkey_not_exist() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ), Error::::NonAssociatedColdKey ); @@ -895,7 +921,8 @@ fn test_swap_owner_new_hotkey_already_exists() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ), Error::::HotKeyAlreadyRegisteredInSubNet ); @@ -937,7 +964,8 @@ fn test_swap_stake_success() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ),); // Verify the swap @@ -1007,7 +1035,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ), Error::::NotEnoughBalanceToPaySwapHotKey ); @@ -1022,7 +1051,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(coldkey), &old_hotkey, &old_hotkey, - Some(netuid) + Some(netuid), + false ), Error::::NewHotKeyIsSameWithOld ); @@ -1035,7 +1065,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ), Error::::HotKeyAlreadyRegisteredInSubNet ); @@ -1047,7 +1078,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(wrong_coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ), Error::::NonAssociatedColdKey ); @@ -1058,7 +1090,8 @@ fn test_swap_hotkey_error_cases() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ),); }); } @@ -1084,7 +1117,8 @@ fn test_swap_child_keys() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ),); // Verify the swap @@ -1095,6 +1129,7 @@ fn test_swap_child_keys() { // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_swap_child_keys_self_loop --exact --show-output #[test] +#[allow(deprecated)] fn test_swap_child_keys_self_loop() { new_test_ext(1).execute_with(|| { let old_hotkey = U256::from(1); @@ -1119,7 +1154,7 @@ fn test_swap_child_keys_self_loop() { RuntimeOrigin::signed(coldkey), old_hotkey, new_hotkey, - Some(netuid) + Some(netuid), ), Error::::InvalidChild ); @@ -1159,7 +1194,8 @@ fn test_swap_parent_keys() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ),); // Verify ParentKeys swap @@ -1204,7 +1240,8 @@ fn test_swap_multiple_subnets() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid1) + Some(netuid1), + false ),); System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); @@ -1212,7 +1249,8 @@ fn test_swap_multiple_subnets() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey_2, - Some(netuid2) + Some(netuid2), + false ),); // Verify the swap for both subnets @@ -1261,7 +1299,8 @@ fn test_swap_complex_parent_child_structure() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ),); // Verify ParentKeys swap @@ -1325,7 +1364,8 @@ fn test_swap_parent_hotkey_childkey_maps() { RuntimeOrigin::signed(coldkey), &parent_old, &parent_new, - Some(netuid) + Some(netuid), + false ),); // Verify parent and child keys updates @@ -1382,7 +1422,8 @@ fn test_swap_child_hotkey_childkey_maps() { RuntimeOrigin::signed(coldkey), &child_old, &child_new, - Some(netuid) + Some(netuid), + false ),); // Verify parent and child keys updates @@ -1422,7 +1463,8 @@ fn test_swap_hotkey_is_sn_owner_hotkey() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ),); // Check for SubnetOwnerHotkey @@ -1458,7 +1500,8 @@ fn test_swap_hotkey_swap_rate_limits() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ),); // Check for new hotkey @@ -1492,7 +1535,8 @@ fn test_swap_owner_failed_interval_not_passed() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false ), Error::::HotKeySwapOnSubnetIntervalNotPassed, ); @@ -1515,7 +1559,8 @@ fn test_swap_owner_check_swap_block_set() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert_eq!( @@ -1540,7 +1585,8 @@ fn test_swap_owner_check_swap_record_clean_up() { RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey, - Some(netuid) + Some(netuid), + false )); assert_eq!( @@ -1555,35 +1601,844 @@ fn test_swap_owner_check_swap_record_clean_up() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_hotkey_error_cases --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_hotkey_swap_stake_is_not_lost --exact --nocapture #[test] -fn test_swap_hotkey_registered_on_other_subnet() { +fn test_revert_hotkey_swap_stake_is_not_lost() { new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + let netuid2 = NetUid::from(2); + let tempo: u16 = 13; + let hk1 = U256::from(1); + let hk2 = U256::from(2); + let coldkey = U256::from(3); + let swap_cost = 1_000_000_000u64 * 2; + let stake2 = 1_000_000_000u64; + + // Setup + add_network(netuid, tempo, 0); + add_network(netuid2, tempo, 0); + register_ok_neuron(netuid, hk1, coldkey, 0); + register_ok_neuron(netuid2, hk1, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost.into()); + + let hk1_stake_before_increase = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid); + assert!( + hk1_stake_before_increase == 0.into(), + "hk1 should have empty stake" + ); + + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hk1, + &coldkey, + netuid, + 1_000_000_000u64.into(), + ); + + let hk1_stake_before_swap = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid); + assert!( + hk1_stake_before_swap == 1_000_000_000.into(), + "hk1 should have stake before swap" + ); + + step_block(20); + + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &hk1, + &hk2, + Some(netuid), + false + )); + + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hk1, + &coldkey, + netuid, + stake2.into(), + ); + + step_block(20); + + let hk2_stake_before_revert = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk2, &coldkey, netuid); + let hk1_stake_before_revert = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid); + + assert_eq!(hk1_stake_before_revert, stake2.into()); + + // Revert: hk2 -> hk1 + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &hk2, + &hk1, + Some(netuid), + false + )); + + let hk1_stake_after_revert = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid); + let hk2_stake_after_revert = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk2, &coldkey, netuid); + + assert_eq!( + hk1_stake_after_revert, + hk2_stake_before_revert + stake2.into(), + ); + + // hk2 should be empty + assert_eq!( + hk2_stake_after_revert, + 0.into(), + "hk2 should have no stake after revert" + ); + }); +} + +// Check swap hotkey with keep_stake doesn't affect stake and related storage maps +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_hotkey_swap_keep_stake --exact --nocapture +#[test] +fn test_hotkey_swap_keep_stake() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + let tempo: u16 = 13; let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); + let child_key = U256::from(4); let coldkey = U256::from(3); - let wrong_coldkey = U256::from(4); - let netuid = add_dynamic_network(&old_hotkey, &coldkey); - let other_netuid = add_dynamic_network(&old_hotkey, &coldkey); + let swap_cost = 1_000_000_000u64 * 2; + let stake_amount = 1_000_000_000u64; + let voting_power_value = 5_000_000_000_000_u64; - // Set up initial state - Owner::::insert(old_hotkey, coldkey); - TotalNetworks::::put(1); + // Setup + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost.into()); - let initial_balance = SubtensorModule::get_key_swap_cost() + 1000.into(); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, initial_balance); + VotingPower::::insert(netuid, old_hotkey, voting_power_value); + assert_eq!( + SubtensorModule::get_voting_power(netuid, &old_hotkey), + voting_power_value + ); - // Test new hotkey already registered on other subnet - IsNetworkMember::::insert(new_hotkey, other_netuid, true); - System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); - assert_noop!( - SubtensorModule::do_swap_hotkey( - RuntimeOrigin::signed(coldkey), + ChildKeys::::insert(old_hotkey, netuid, vec![(u64::MAX, child_key)]); + ParentKeys::::insert(child_key, netuid, vec![(u64::MAX, old_hotkey)]); + + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &coldkey, + netuid, + stake_amount.into(), + ); + + assert!(SubtensorModule::is_hotkey_registered_on_network( + netuid, + &old_hotkey + )); + + step_block(20); + + let old_hotkey_stake_before_swap = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &old_hotkey, - &new_hotkey, - Some(netuid) - ), - Error::::HotKeyAlreadyRegisteredInSubNet + &coldkey, + netuid, + ); + + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid), + true + )); + + let old_hotkey_stake_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &coldkey, + netuid, + ); + assert_eq!( + old_hotkey_stake_after, old_hotkey_stake_before_swap, + "old_hotkey stake must NOT change during keep_stake swap" + ); + + let new_hotkey_stake_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &coldkey, + netuid, + ); + assert_eq!( + new_hotkey_stake_after, + 0.into(), + "new_hotkey should have no stake" + ); + + assert!( + SubtensorModule::is_hotkey_registered_on_network(netuid, &new_hotkey), + "new_hotkey should be registered on netuid" + ); + + assert!( + !SubtensorModule::is_hotkey_registered_on_network(netuid, &old_hotkey), + "old_hotkey should NOT be registered on netuid after swap" + ); + + let root_total_alpha = TotalHotkeyAlpha::::get(old_hotkey, netuid); + let child_total_alpha = TotalHotkeyAlpha::::get(new_hotkey, netuid); + assert!( + root_total_alpha > 0.into(), + "old_hotkey should retain TotalHotkeyAlpha" + ); + assert_eq!( + child_total_alpha, + 0.into(), + "new_hotkey should have zero TotalHotkeyAlpha" + ); + + let root_voting_power = VotingPower::::get(netuid, old_hotkey); + let child_voting_power = VotingPower::::get(netuid, new_hotkey); + assert!( + root_voting_power > 0, + "old_hotkey should retain VotingPower" + ); + assert_eq!( + child_voting_power, 0, + "new_hotkey should have zero VotingPower" + ); + + let old_hotkey_children = ChildKeys::::get(old_hotkey, netuid); + assert!( + !old_hotkey_children.iter().any(|(_, c)| *c == child_key), + "old_hotkey should NOT retain ChildKeys after swap" + ); + let new_hotkey_children = ChildKeys::::get(new_hotkey, netuid); + assert!( + new_hotkey_children.iter().any(|(_, c)| *c == child_key), + "new_hotkey should inherit ChildKeys from old_hotkey" + ); + + let child_key_parents = ParentKeys::::get(child_key, netuid); + assert!( + child_key_parents.iter().any(|(_, p)| *p == new_hotkey), + "child_key should have new_hotkey as parent after swap" + ); + assert!( + !child_key_parents.iter().any(|(_, p)| *p == old_hotkey), + "child_key should NOT have old_hotkey as parent after swap" + ); + }); +} +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_hotkey_swap --exact --nocapture +// This test confirms, that the old hotkey can be reverted after the hotkey swap +#[test] +fn test_revert_hotkey_swap() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + let netuid2 = NetUid::from(2); + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let swap_cost = 1_000_000_000u64 * 2; + + // Setup initial state + add_network(netuid, tempo, 0); + add_network(netuid2, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + register_ok_neuron(netuid2, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost.into()); + step_block(20); + + // Perform the first swap (only on netuid) + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid), + false + )); + + assert!(SubtensorModule::is_hotkey_registered_on_any_network( + &old_hotkey + )); + + step_block(20); + + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &new_hotkey, + &old_hotkey, + Some(netuid), + false + )); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_hotkey_swap_parent_hotkey_childkey_maps --exact --nocapture +#[test] +fn test_revert_hotkey_swap_parent_hotkey_childkey_maps() { + new_test_ext(1).execute_with(|| { + let hk1 = U256::from(1); + let coldkey = U256::from(2); + let child = U256::from(3); + let child_other = U256::from(4); + let hk2 = U256::from(5); + + let netuid = add_dynamic_network(&hk1, &coldkey); + let netuid2 = add_dynamic_network(&hk1, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); + SubtensorModule::create_account_if_non_existent(&coldkey, &hk1); + + mock_set_children(&coldkey, &hk1, netuid, &[(u64::MAX, child)]); + step_rate_limit(&TransactionType::SetChildren, netuid); + mock_schedule_children(&coldkey, &hk1, netuid, &[(u64::MAX, child_other)]); + + assert_eq!( + ParentKeys::::get(child, netuid), + vec![(u64::MAX, hk1)] + ); + assert_eq!(ChildKeys::::get(hk1, netuid), vec![(u64::MAX, child)]); + let existing_pending_child_keys = PendingChildKeys::::get(netuid, hk1); + assert_eq!(existing_pending_child_keys.0, vec![(u64::MAX, child_other)]); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk1, + &hk2, + Some(netuid), + false + )); + + assert_eq!( + ParentKeys::::get(child, netuid), + vec![(u64::MAX, hk2)] + ); + assert_eq!(ChildKeys::::get(hk2, netuid), vec![(u64::MAX, child)]); + assert_eq!( + PendingChildKeys::::get(netuid, hk2), + existing_pending_child_keys + ); + assert!(ChildKeys::::get(hk1, netuid).is_empty()); + assert!(PendingChildKeys::::get(netuid, hk1).0.is_empty()); + + // Revert: hk2 -> hk1 + step_block(20); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk2, + &hk1, + Some(netuid), + false + )); + + assert_eq!( + ParentKeys::::get(child, netuid), + vec![(u64::MAX, hk1)], + "ParentKeys must point back to hk1 after revert" + ); + assert_eq!( + ChildKeys::::get(hk1, netuid), + vec![(u64::MAX, child)], + "ChildKeys must be restored to hk1 after revert" + ); + assert_eq!( + PendingChildKeys::::get(netuid, hk1), + existing_pending_child_keys, + "PendingChildKeys must be restored to hk1 after revert" + ); + + assert!( + ChildKeys::::get(hk2, netuid).is_empty(), + "hk2 must have no ChildKeys after revert" + ); + assert!( + PendingChildKeys::::get(netuid, hk2).0.is_empty(), + "hk2 must have no PendingChildKeys after revert" + ); + }) +} +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_hotkey_swap_uids_and_keys --exact --nocapture +#[test] +fn test_revert_hotkey_swap_uids_and_keys() { + new_test_ext(1).execute_with(|| { + let uid = 5u16; + let hk1 = U256::from(1); + let hk2 = U256::from(2); + let coldkey = U256::from(3); + + let netuid = add_dynamic_network(&hk1, &coldkey); + let netuid2 = add_dynamic_network(&hk1, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); + + IsNetworkMember::::insert(hk1, netuid, true); + Uids::::insert(netuid, hk1, uid); + Keys::::insert(netuid, uid, hk1); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk1, + &hk2, + Some(netuid), + false + )); + + assert_eq!(Uids::::get(netuid, hk1), None); + assert_eq!(Uids::::get(netuid, hk2), Some(uid)); + assert_eq!(Keys::::get(netuid, uid), hk2); + + // Revert: hk2 -> hk1 + step_block(20); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk2, + &hk1, + Some(netuid), + false + )); + + assert_eq!( + Uids::::get(netuid, hk2), + None, + "hk2 must have no uid after revert" + ); + assert_eq!( + Uids::::get(netuid, hk1), + Some(uid), + "hk1 must have its uid restored after revert" + ); + assert_eq!( + Keys::::get(netuid, uid), + hk1, + "Keys must point back to hk1 after revert" + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_hotkey_swap_auto_stake_destination --exact --nocapture +#[test] +fn test_revert_hotkey_swap_auto_stake_destination() { + new_test_ext(1).execute_with(|| { + let hk1 = U256::from(1); + let hk2 = U256::from(2); + let coldkey = U256::from(3); + let netuid = NetUid::from(2u16); + let netuid2 = NetUid::from(3u16); + let staker1 = U256::from(4); + let staker2 = U256::from(5); + let coldkeys = vec![staker1, staker2, coldkey]; + + add_network(netuid, 1, 0); + add_network(netuid2, 1, 0); + register_ok_neuron(netuid, hk1, coldkey, 0); + register_ok_neuron(netuid2, hk1, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); + + AutoStakeDestinationColdkeys::::insert(hk1, netuid, coldkeys.clone()); + AutoStakeDestination::::insert(coldkey, netuid, hk1); + AutoStakeDestination::::insert(staker1, netuid, hk1); + AutoStakeDestination::::insert(staker2, netuid, hk1); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk1, + &hk2, + Some(netuid), + false + )); + + assert_eq!( + AutoStakeDestinationColdkeys::::get(hk2, netuid), + coldkeys + ); + assert!(AutoStakeDestinationColdkeys::::get(hk1, netuid).is_empty()); + assert_eq!( + AutoStakeDestination::::get(coldkey, netuid), + Some(hk2) + ); + assert_eq!( + AutoStakeDestination::::get(staker1, netuid), + Some(hk2) + ); + assert_eq!( + AutoStakeDestination::::get(staker2, netuid), + Some(hk2) + ); + + // Revert: hk2 -> hk1 + step_block(20); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk2, + &hk1, + Some(netuid), + false + )); + + assert_eq!( + AutoStakeDestinationColdkeys::::get(hk1, netuid), + coldkeys, + "AutoStakeDestinationColdkeys must be restored to hk1 after revert" + ); + assert!( + AutoStakeDestinationColdkeys::::get(hk2, netuid).is_empty(), + "hk2 must have no AutoStakeDestinationColdkeys after revert" + ); + assert_eq!( + AutoStakeDestination::::get(coldkey, netuid), + Some(hk1), + "coldkey AutoStakeDestination must point back to hk1 after revert" + ); + assert_eq!( + AutoStakeDestination::::get(staker1, netuid), + Some(hk1), + "staker1 AutoStakeDestination must point back to hk1 after revert" + ); + assert_eq!( + AutoStakeDestination::::get(staker2, netuid), + Some(hk1), + "staker2 AutoStakeDestination must point back to hk1 after revert" + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_hotkey_swap_subnet_owner --exact --nocapture +#[test] +fn test_revert_hotkey_swap_subnet_owner() { + new_test_ext(1).execute_with(|| { + let hk1 = U256::from(1); + let hk2 = U256::from(2); + let coldkey = U256::from(3); + + let netuid = add_dynamic_network(&hk1, &coldkey); + let netuid2 = add_dynamic_network(&hk1, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); + + assert_eq!(SubnetOwnerHotkey::::get(netuid), hk1); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk1, + &hk2, + Some(netuid), + false + )); + + assert_eq!( + SubnetOwnerHotkey::::get(netuid), + hk2, + "hk2 must be subnet owner after swap" + ); + + // Revert: hk2 -> hk1 + step_block(20); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk2, + &hk1, + Some(netuid), + false + )); + + assert_eq!( + SubnetOwnerHotkey::::get(netuid), + hk1, + "hk1 must be restored as subnet owner after revert" + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_hotkey_swap_dividends --exact --nocapture +#[test] +fn test_revert_hotkey_swap_dividends() { + new_test_ext(1).execute_with(|| { + let hk1 = U256::from(1); + let hk2 = U256::from(2); + let coldkey = U256::from(3); + + let netuid = add_dynamic_network(&hk1, &coldkey); + let netuid2 = add_dynamic_network(&hk1, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX.into()); + + let amount = 10_000; + let shares = U64F64::from_num(123456); + + TotalHotkeyAlpha::::insert(hk1, netuid, AlphaBalance::from(amount)); + TotalHotkeyAlphaLastEpoch::::insert(hk1, netuid, AlphaBalance::from(amount * 2)); + TotalHotkeyShares::::insert(hk1, netuid, U64F64::from_num(shares)); + Alpha::::insert((hk1, coldkey, netuid), U64F64::from_num(amount)); + AlphaDividendsPerSubnet::::insert(netuid, hk1, AlphaBalance::from(amount)); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk1, + &hk2, + Some(netuid), + false + )); + + assert_eq!( + TotalHotkeyAlpha::::get(hk1, netuid), + AlphaBalance::ZERO + ); + assert_eq!( + TotalHotkeyAlpha::::get(hk2, netuid), + AlphaBalance::from(amount) + ); + assert_eq!( + TotalHotkeyAlphaLastEpoch::::get(hk1, netuid), + AlphaBalance::ZERO + ); + assert_eq!( + TotalHotkeyAlphaLastEpoch::::get(hk2, netuid), + AlphaBalance::from(amount * 2) + ); + assert_eq!( + TotalHotkeyShares::::get(hk1, netuid), + U64F64::from_num(0) + ); + assert_eq!( + TotalHotkeyShares::::get(hk2, netuid), + U64F64::from_num(shares) + ); + assert_eq!( + Alpha::::get((hk1, coldkey, netuid)), + U64F64::from_num(0) + ); + assert_eq!( + Alpha::::get((hk2, coldkey, netuid)), + U64F64::from_num(amount) + ); + assert_eq!( + AlphaDividendsPerSubnet::::get(netuid, hk1), + AlphaBalance::ZERO + ); + assert_eq!( + AlphaDividendsPerSubnet::::get(netuid, hk2), + AlphaBalance::from(amount) + ); + + // Revert: hk2 -> hk1 + step_block(20); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &hk2, + &hk1, + Some(netuid), + false + )); + + assert_eq!( + TotalHotkeyAlpha::::get(hk2, netuid), + AlphaBalance::ZERO, + "hk2 TotalHotkeyAlpha must be zero after revert" + ); + assert_eq!( + TotalHotkeyAlpha::::get(hk1, netuid), + AlphaBalance::from(amount), + "hk1 TotalHotkeyAlpha must be restored after revert" + ); + assert_eq!( + TotalHotkeyAlphaLastEpoch::::get(hk2, netuid), + AlphaBalance::ZERO, + "hk2 TotalHotkeyAlphaLastEpoch must be zero after revert" + ); + assert_eq!( + TotalHotkeyAlphaLastEpoch::::get(hk1, netuid), + AlphaBalance::from(amount * 2), + "hk1 TotalHotkeyAlphaLastEpoch must be restored after revert" + ); + assert_eq!( + TotalHotkeyShares::::get(hk2, netuid), + U64F64::from_num(0), + "hk2 TotalHotkeyShares must be zero after revert" + ); + assert_eq!( + TotalHotkeyShares::::get(hk1, netuid), + U64F64::from_num(shares), + "hk1 TotalHotkeyShares must be restored after revert" + ); + assert_eq!( + Alpha::::get((hk2, coldkey, netuid)), + U64F64::from_num(0), + "hk2 Alpha must be zero after revert" + ); + assert_eq!( + Alpha::::get((hk1, coldkey, netuid)), + U64F64::from_num(amount), + "hk1 Alpha must be restored after revert" + ); + assert_eq!( + AlphaDividendsPerSubnet::::get(netuid, hk2), + AlphaBalance::ZERO, + "hk2 AlphaDividendsPerSubnet must be zero after revert" + ); + assert_eq!( + AlphaDividendsPerSubnet::::get(netuid, hk1), + AlphaBalance::from(amount), + "hk1 AlphaDividendsPerSubnet must be restored after revert" + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_voting_power_transfers_on_hotkey_swap --exact --nocapture +#[test] +fn test_revert_voting_power_transfers_on_hotkey_swap() { + new_test_ext(1).execute_with(|| { + let hk1 = U256::from(1); + let hk2 = U256::from(99); + let coldkey = U256::from(2); + let netuid = add_dynamic_network(&hk1, &coldkey); + let voting_power_value = 5_000_000_000_000_u64; + + VotingPower::::insert(netuid, hk1, voting_power_value); + assert_eq!( + SubtensorModule::get_voting_power(netuid, &hk1), + voting_power_value + ); + assert_eq!(SubtensorModule::get_voting_power(netuid, &hk2), 0); + + SubtensorModule::swap_voting_power_for_hotkey(&hk1, &hk2, netuid); + + assert_eq!(SubtensorModule::get_voting_power(netuid, &hk1), 0); + assert_eq!( + SubtensorModule::get_voting_power(netuid, &hk2), + voting_power_value + ); + + // Revert: hk2 -> hk1 + SubtensorModule::swap_voting_power_for_hotkey(&hk2, &hk1, netuid); + + assert_eq!( + SubtensorModule::get_voting_power(netuid, &hk1), + voting_power_value, + "hk1 voting power must be fully restored after revert" + ); + assert_eq!( + SubtensorModule::get_voting_power(netuid, &hk2), + 0, + "hk2 must have no voting power after revert" + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey_with_subnet::test_revert_claim_root_with_swap_hotkey --exact --nocapture +#[test] +fn test_revert_claim_root_with_swap_hotkey() { + new_test_ext(1).execute_with(|| { + let owner_coldkey = U256::from(1001); + let hk1 = U256::from(1002); + let hk2 = U256::from(1003); + let coldkey = U256::from(1004); + + let netuid = add_dynamic_network(&hk1, &owner_coldkey); + let netuid2 = add_dynamic_network(&hk1, &owner_coldkey); + + SubtensorModule::add_balance_to_coldkey_account(&owner_coldkey, u64::MAX.into()); + SubtensorModule::set_tao_weight(u64::MAX); + + let root_stake = 2_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hk1, + &coldkey, + NetUid::ROOT, + root_stake.into(), + ); + + let initial_total_hotkey_alpha = 10_000_000u64; + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hk1, + &owner_coldkey, + netuid, + initial_total_hotkey_alpha.into(), + ); + + let pending_root_alpha = 1_000_000u64; + SubtensorModule::distribute_emission( + netuid, + AlphaBalance::ZERO, + AlphaBalance::ZERO, + pending_root_alpha.into(), + AlphaBalance::ZERO, + ); + + assert_ok!(SubtensorModule::set_root_claim_type( + RuntimeOrigin::signed(coldkey), + RootClaimTypeEnum::Keep + )); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); + + let stake_after_claim: u64 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hk1, &coldkey, netuid) + .into(); + + let hk1_root_claimed = RootClaimed::::get((netuid, &hk1, &coldkey)); + let hk1_claimable = *RootClaimable::::get(hk1).get(&netuid).unwrap(); + + assert_eq!(u128::from(stake_after_claim), hk1_root_claimed); + assert!(!RootClaimable::::get(hk2).contains_key(&netuid)); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(owner_coldkey), + &hk1, + &hk2, + Some(netuid), + false + )); + + assert_eq!( + RootClaimed::::get((netuid, &hk1, &coldkey)), + 0u128, + "hk1 RootClaimed must be zero after swap" + ); + assert_eq!( + RootClaimed::::get((netuid, &hk2, &coldkey)), + hk1_root_claimed, + "hk2 must have hk1's RootClaimed after swap" + ); + assert!(!RootClaimable::::get(hk1).contains_key(&netuid)); + assert_eq!( + *RootClaimable::::get(hk2).get(&netuid).unwrap(), + hk1_claimable, + "hk2 must have hk1's RootClaimable after swap" + ); + + // Revert: hk2 -> hk1 + step_block(20); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(owner_coldkey), + &hk2, + &hk1, + Some(netuid), + false + )); + + assert_eq!( + RootClaimed::::get((netuid, &hk2, &coldkey)), + 0u128, + "hk2 RootClaimed must be zero after revert" + ); + assert_eq!( + RootClaimed::::get((netuid, &hk1, &coldkey)), + hk1_root_claimed, + "hk1 RootClaimed must be restored after revert" + ); + + assert!(!RootClaimable::::get(hk2).contains_key(&netuid)); + assert_eq!( + *RootClaimable::::get(hk1).get(&netuid).unwrap(), + hk1_claimable, + "hk1 RootClaimable must be restored after revert" ); }); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f1231df83d..5e3436a78c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -268,7 +268,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: 389, + spec_version: 390, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -1117,7 +1117,7 @@ parameter_types! { // 0 days pub const InitialStartCallDelay: u64 = 0; pub const SubtensorInitialKeySwapOnSubnetCost: TaoBalance = TaoBalance::new(1_000_000); // 0.001 TAO - pub const HotkeySwapOnSubnetInterval : BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days + pub const HotkeySwapOnSubnetInterval : BlockNumber = 24 * 60 * 60 / 12; // 1 day pub const LeaseDividendsDistributionInterval: BlockNumber = 100; // 100 blocks pub const MaxImmuneUidsPercentage: Percent = Percent::from_percent(80); pub const EvmKeyAssociateRateLimit: u64 = EVM_KEY_ASSOCIATE_RATELIMIT;