diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index f8b3e6a9b6..960348cecd 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -140,6 +140,8 @@ parameter_types! { pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks pub const DurationOfStartCall: u64 = 7 * 24 * 60 * 60 / 12; // 7 days + pub const InitialKeySwapOnSubnetCost: u64 = 10_000_000; + pub const HotkeySwapOnSubnetInterval: u64 = 7 * 24 * 60 * 60 / 12; // 7 days } impl pallet_subtensor::Config for Test { @@ -209,6 +211,8 @@ impl pallet_subtensor::Config for Test { type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall; + type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost; + type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index a7cd03e652..eaad64199e 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1454,7 +1454,12 @@ mod pallet_benchmarks { Subtensor::::add_balance_to_coldkey_account(&coldkey, cost); #[extrinsic_call] - _(RawOrigin::Signed(coldkey.clone()), old.clone(), new.clone()); + _( + RawOrigin::Signed(coldkey.clone()), + old.clone(), + new.clone(), + None, + ); } #[benchmark] diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f4f511e74a..3c74adf5d5 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -940,6 +940,19 @@ pub mod pallet { OptionQuery, >; + #[pallet::storage] + /// --- DMap ( netuid, coldkey ) --> blocknumber | last hotkey swap on network. + pub type LastHotkeySwapOnNetuid = StorageDoubleMap< + _, + Identity, + u16, + Blake2_128Concat, + T::AccountId, + u64, + ValueQuery, + DefaultZeroU64, + >; + #[pallet::storage] /// Ensures unique IDs for StakeJobs storage map pub type NextStakeJobId = StorageValue<_, u64, ValueQuery, DefaultZeroU64>; diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 4377d9f016..b0f2483331 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -224,5 +224,11 @@ mod config { /// Block number after a new subnet accept the start call extrinsic. #[pallet::constant] type DurationOfStartCall: Get; + /// Cost of swapping a hotkey in a subnet. + #[pallet::constant] + type KeySwapOnSubnetCost: Get; + /// Block number for a coldkey swap the hotkey in specific subnet. + #[pallet::constant] + type HotkeySwapOnSubnetInterval: Get; } } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 650fb50451..434291c45d 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -935,17 +935,18 @@ mod dispatches { Self::do_burned_registration(origin, netuid, hotkey) } - /// The extrinsic for user to change its hotkey + /// The extrinsic for user to change its hotkey in subnet or all subnets. #[pallet::call_index(70)] - #[pallet::weight((Weight::from_parts(240_600_000, 0) - .saturating_add(T::DbWeight::get().reads(31)) - .saturating_add(T::DbWeight::get().writes(23)), DispatchClass::Operational, Pays::No))] + #[pallet::weight((Weight::from_parts(285_900_000, 0) + .saturating_add(T::DbWeight::get().reads(47)) + .saturating_add(T::DbWeight::get().writes(37)), DispatchClass::Operational, Pays::No))] pub fn swap_hotkey( origin: OriginFor, hotkey: T::AccountId, new_hotkey: T::AccountId, + netuid: Option, ) -> DispatchResultWithPostInfo { - Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) + Self::do_swap_hotkey(origin, &hotkey, &new_hotkey, netuid) } /// The extrinsic for user to change the coldkey associated with their account. diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 2a8e5bc346..6e701014ae 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -210,6 +210,8 @@ mod errors { InvalidRecoveredPublicKey, /// SubToken disabled now SubtokenDisabled, + /// Too frequent hotkey swap on subnet + HotKeySwapOnSubnetIntervalNotPassed, /// Zero max stake amount ZeroMaxStakeAmount, /// Invalid netuid duplication diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 9849a517ee..81694275f0 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -351,5 +351,17 @@ mod events { /// - **netuid**: The network identifier. /// - **Enabled**: Is Commit-Reveal enabled. CommitRevealEnabled(u16, bool), + + /// the hotkey is swapped + HotkeySwappedOnSubnet { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of old hotkey + old_hotkey: T::AccountId, + /// the account ID of new hotkey + new_hotkey: T::AccountId, + /// the subnet ID + netuid: u16, + }, } } diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index a0e2fc6e72..d4a2feead0 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -14,7 +14,9 @@ mod hooks { // # Args: // * 'n': (BlockNumberFor): // - The number of the block we are initializing. - fn on_initialize(_block_number: BlockNumberFor) -> Weight { + fn on_initialize(block_number: BlockNumberFor) -> Weight { + let hotkey_swap_clean_up_weight = Self::clean_up_hotkey_swap_records(block_number); + let block_step_result = Self::block_step(); match block_step_result { Ok(_) => { @@ -23,6 +25,7 @@ mod hooks { Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) + .saturating_add(hotkey_swap_clean_up_weight) } Err(e) => { // --- If the block step was unsuccessful, return the weight anyway. @@ -30,6 +33,7 @@ mod hooks { Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) + .saturating_add(hotkey_swap_clean_up_weight) } } } @@ -127,4 +131,42 @@ mod hooks { Ok(()) } } + + impl Pallet { + // This function is to clean up the old hotkey swap records + // It just clean up for one subnet at a time, according to the block number + fn clean_up_hotkey_swap_records(block_number: BlockNumberFor) -> Weight { + let mut weight = Weight::from_parts(0, 0); + let hotkey_swap_on_subnet_interval = T::HotkeySwapOnSubnetInterval::get(); + let block_number: u64 = TryInto::try_into(block_number) + .ok() + .expect("blockchain will not exceed 2^64 blocks; QED."); + weight.saturating_accrue(T::DbWeight::get().reads(2_u64)); + + let netuids = Self::get_all_subnet_netuids(); + weight.saturating_accrue(T::DbWeight::get().reads(netuids.len() as u64)); + + if let Some(slot) = block_number.checked_rem(hotkey_swap_on_subnet_interval) { + // only handle the subnet with the same residue as current block number by HotkeySwapOnSubnetInterval + for netuid in netuids.iter().filter(|netuid| { + (**netuid as u64).checked_rem(hotkey_swap_on_subnet_interval) == Some(slot) + }) { + // Iterate over all the coldkeys in the subnet + for (coldkey, swap_block_number) in + LastHotkeySwapOnNetuid::::iter_prefix(netuid) + { + // Clean up out of date swap records + if swap_block_number.saturating_add(hotkey_swap_on_subnet_interval) + < block_number + { + LastHotkeySwapOnNetuid::::remove(netuid, coldkey); + weight.saturating_accrue(T::DbWeight::get().writes(1_u64)); + } + weight.saturating_accrue(T::DbWeight::get().reads(1_u64)); + } + } + } + weight + } + } } diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index c97252677c..008fc03669 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -187,4 +187,10 @@ impl Pallet { } false } + + /// Return true if a hotkey is registered on specific network. + /// + pub fn is_hotkey_registered_on_specific_network(hotkey: &T::AccountId, netuid: u16) -> bool { + IsNetworkMember::::contains_key(hotkey, netuid) + } } diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 54c7c01d8e..733535c2ad 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -2,7 +2,6 @@ use super::*; use frame_support::weights::Weight; use sp_core::Get; use substrate_fixed::types::U64F64; - impl Pallet { /// Swaps the hotkey of a coldkey account. /// @@ -11,6 +10,7 @@ impl Pallet { /// * `origin` - The origin of the transaction, and also the coldkey account. /// * `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. /// /// # Returns /// @@ -27,76 +27,98 @@ impl Pallet { origin: T::RuntimeOrigin, old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, + netuid: Option, ) -> DispatchResultWithPostInfo { // 1. Ensure the origin is signed and get the coldkey let coldkey = ensure_signed(origin)?; - // 2. Initialize the weight for this operation - let mut weight = T::DbWeight::get().reads(2); - - // 3. Ensure the new hotkey is different from the old one - ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); - - // 4. Ensure the new hotkey is not already registered on any network - ensure!( - !Self::is_hotkey_registered_on_any_network(new_hotkey), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - - // 5. Update the weight for the checks above - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 0)); - - // 6. Ensure the coldkey owns the old hotkey + // 2. Ensure the coldkey owns the old hotkey ensure!( Self::coldkey_owns_hotkey(&coldkey, old_hotkey), Error::::NonAssociatedColdKey ); - // 7. Get the current block number + // 3. Initialize the weight for this operation + let mut weight = T::DbWeight::get().reads(2); + + // 4. Ensure the new hotkey is different from the old one + ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); + + // 5. Get the current block number let block: u64 = Self::get_current_block_as_u64(); - // 8. Ensure the transaction rate limit is not exceeded + // 6. Ensure the transaction rate limit is not exceeded ensure!( !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), Error::::HotKeySetTxRateLimitExceeded ); - // 9. Update the weight for reading the total networks - weight.saturating_accrue( - T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), + weight.saturating_accrue(T::DbWeight::get().reads(2)); + + // 7. Swap LastTxBlock + // LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey. + let last_tx_block: u64 = LastTxBlock::::get(old_hotkey); + LastTxBlock::::insert(new_hotkey, last_tx_block); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // 8. Swap LastTxBlockDelegateTake + // LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take. + let last_tx_block_delegate_take: u64 = LastTxBlockDelegateTake::::get(old_hotkey); + LastTxBlockDelegateTake::::insert(new_hotkey, last_tx_block_delegate_take); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // 9. Swap LastTxBlockChildKeyTake + // LastTxBlockChildKeyTake( hotkey ) --> u64 -- the last transaction block for the hotkey child key take. + let last_tx_block_child_key_take: u64 = LastTxBlockChildKeyTake::::get(old_hotkey); + LastTxBlockChildKeyTake::::insert(new_hotkey, last_tx_block_child_key_take); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // 10. 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); + }; + + // Start to do everything for swap hotkey on all subnets case + // 11. Ensure the new hotkey is not already registered on any network + ensure!( + !Self::is_hotkey_registered_on_any_network(new_hotkey), + Error::::HotKeyAlreadyRegisteredInSubNet ); - // 10. Get the cost for swapping the key + // 12. Get the cost for swapping the key let swap_cost = Self::get_key_swap_cost(); log::debug!("Swap cost: {:?}", swap_cost); - // 11. Ensure the coldkey has enough balance to pay for the swap + // 13. Ensure the coldkey has enough balance to pay for the swap ensure!( Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapHotKey ); - // 12. Remove the swap cost from the coldkey's account + weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 0)); + + // 14. Remove the swap cost from the coldkey's account let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; - // 13. Burn the tokens + // 18. Burn the tokens Self::burn_tokens(actual_burn_amount); + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); - // 14. Perform the hotkey swap - let _ = Self::perform_hotkey_swap(old_hotkey, new_hotkey, &coldkey, &mut weight); + // 19. Perform the hotkey swap + Self::perform_hotkey_swap_on_all_subnets(old_hotkey, new_hotkey, &coldkey, &mut weight)?; - // 15. Update the last transaction block for the coldkey + // 20. Update the last transaction block for the coldkey Self::set_last_tx_block(&coldkey, block); weight.saturating_accrue(T::DbWeight::get().writes(1)); - // 16. Emit an event for the hotkey swap + // 21. Emit an event for the hotkey swap Self::deposit_event(Event::HotkeySwapped { coldkey, old_hotkey: old_hotkey.clone(), new_hotkey: new_hotkey.clone(), }); - // 17. Return the weight of the operation + // 22. Return the weight of the operation Ok(Some(weight).into()) } @@ -134,87 +156,65 @@ impl Pallet { /// # Note /// This function performs extensive storage reads and writes, which can be computationally expensive. /// The accumulated weight should be carefully considered in the context of block limits. - pub fn perform_hotkey_swap( + pub fn perform_hotkey_swap_on_all_subnets( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, coldkey: &T::AccountId, weight: &mut Weight, ) -> DispatchResult { - // 1. Swap owner. + // 1. keep the old hotkey alpha values for the case where hotkey staked by multiple coldkeys. + let old_alpha_values: Vec<((T::AccountId, u16), U64F64)> = + Alpha::::iter_prefix((old_hotkey,)).collect(); + weight.saturating_accrue(T::DbWeight::get().reads(old_alpha_values.len() as u64)); + + // 2. Swap owner. // Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey. Owner::::remove(old_hotkey); Owner::::insert(new_hotkey, coldkey.clone()); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 2. Swap OwnedHotkeys. + // 3. Swap OwnedHotkeys. // OwnedHotkeys( coldkey ) -> Vec -- the hotkeys that the coldkey owns. let mut hotkeys = OwnedHotkeys::::get(coldkey); // Add the new key if needed. if !hotkeys.contains(new_hotkey) { hotkeys.push(new_hotkey.clone()); } - // Remove the old key. + + // 4. Remove the old key. hotkeys.retain(|hk| *hk != *old_hotkey); OwnedHotkeys::::insert(coldkey, hotkeys); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 3. 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. - TotalHotkeyAlpha::::iter_prefix(old_hotkey) - .drain() - .for_each(|(netuid, old_alpha)| { - let new_total_hotkey_alpha = TotalHotkeyAlpha::::get(new_hotkey, netuid); - TotalHotkeyAlpha::::insert( - new_hotkey, - netuid, - old_alpha.saturating_add(new_total_hotkey_alpha), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - }); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 4. 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. - TotalHotkeyShares::::iter_prefix(old_hotkey) - .drain() - .for_each(|(netuid, old_shares)| { - let new_total_hotkey_shares = TotalHotkeyShares::::get(new_hotkey, netuid); - TotalHotkeyShares::::insert( - new_hotkey, - netuid, - old_shares.saturating_add(new_total_hotkey_shares), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - }); + // 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); + } - // 5. Swap LastTxBlock + // 6. Swap LastTxBlock // LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey. - let last_tx_block: u64 = LastTxBlock::::get(old_hotkey); LastTxBlock::::remove(old_hotkey); - LastTxBlock::::insert(new_hotkey, last_tx_block); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // 6. Swap LastTxBlockDelegateTake + // 7. Swap LastTxBlockDelegateTake // LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take. - let last_tx_block_delegate_take: u64 = LastTxBlockDelegateTake::::get(old_hotkey); LastTxBlockDelegateTake::::remove(old_hotkey); - LastTxBlockDelegateTake::::insert(new_hotkey, last_tx_block_delegate_take); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // 7. Swap LastTxBlockChildKeyTake + // 8. Swap LastTxBlockChildKeyTake // LastTxBlockChildKeyTake( hotkey ) --> u64 -- the last transaction block for the hotkey child key take. - let last_tx_block_child_key_take: u64 = LastTxBlockChildKeyTake::::get(old_hotkey); LastTxBlockChildKeyTake::::remove(old_hotkey); - LastTxBlockChildKeyTake::::insert(new_hotkey, last_tx_block_child_key_take); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // 8. Swap Senate members. + // 9. Swap Senate members. // Senate( hotkey ) --> ? if T::SenateMembers::is_member(old_hotkey) { T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } - // 9. Swap delegates. + // 10. Swap delegates. // Delegates( hotkey ) -> take value -- the hotkey delegate take value. if Delegates::::contains_key(old_hotkey) { let old_delegate_take = Delegates::::get(old_hotkey); @@ -223,270 +223,373 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } - // 9. swap PendingHotkeyEmissionOnNetuid - // (DEPRECATED.) - - // 10. Swap all subnet specific info. - let all_netuids: Vec = Self::get_all_subnet_netuids(); - all_netuids.iter().for_each(|netuid| { - // 10.1 Remove the previous hotkey and insert the new hotkey from membership. - // IsNetworkMember( hotkey, netuid ) -> bool -- is the hotkey a subnet member. - let is_network_member: bool = IsNetworkMember::::get(old_hotkey, netuid); - IsNetworkMember::::remove(old_hotkey, netuid); - IsNetworkMember::::insert(new_hotkey, netuid, is_network_member); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - - // 10.2 Swap Uids + Keys. - // Keys( netuid, hotkey ) -> uid -- the uid the hotkey has in the network if it is a member. - // Uids( netuid, hotkey ) -> uid -- the uids that the hotkey has. - if is_network_member { - // 10.2.1 Swap the UIDS - if let Ok(old_uid) = Uids::::try_get(netuid, old_hotkey) { - Uids::::remove(netuid, old_hotkey); - Uids::::insert(netuid, new_hotkey, old_uid); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - - // 10.2.2 Swap the keys. - Keys::::insert(netuid, old_uid, new_hotkey.clone()); - weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); + // 11. 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()); } + StakingHotkeys::::insert(&coldkey, staking_hotkeys); + weight.saturating_accrue(T::DbWeight::get().writes(1)); } + } - // 10.3 Swap Prometheus. - // Prometheus( netuid, hotkey ) -> prometheus -- the prometheus data that a hotkey has in the network. - if is_network_member { - if let Ok(old_prometheus_info) = Prometheus::::try_get(netuid, old_hotkey) { - Prometheus::::remove(netuid, old_hotkey); - Prometheus::::insert(netuid, new_hotkey, old_prometheus_info); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } + // Return successful after swapping all the relevant terms. + Ok(()) + } + + pub fn swap_senate_member( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if T::SenateMembers::is_member(old_hotkey) { + T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + Ok(()) + } + + fn swap_hotkey_on_subnet( + coldkey: &T::AccountId, + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid: u16, + init_weight: Weight, + ) -> DispatchResultWithPostInfo { + // 1. Ensure coldkey not swap hotkey too frequently + let mut weight: Weight = init_weight; + let block: u64 = Self::get_current_block_as_u64(); + let hotkey_swap_interval = T::HotkeySwapOnSubnetInterval::get(); + let last_hotkey_swap_block = LastHotkeySwapOnNetuid::::get(netuid, coldkey); + + ensure!( + last_hotkey_swap_block.saturating_add(hotkey_swap_interval) < block, + Error::::HotKeySwapOnSubnetIntervalNotPassed + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 0)); + + // 2. Ensure the hotkey not registered on the network before. + ensure!( + !Self::is_hotkey_registered_on_specific_network(new_hotkey, netuid), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); + + // 3. Get the cost for swapping the key on the subnet + let swap_cost = T::KeySwapOnSubnetCost::get(); + log::debug!("Swap cost in subnet {:?}: {:?}", netuid, swap_cost); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); + + // 4. Ensure the coldkey has enough balance to pay for the swap + ensure!( + Self::can_remove_balance_from_coldkey_account(coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapHotKey + ); + + // 5. Remove the swap cost from the coldkey's account + let actual_burn_amount = Self::remove_balance_from_coldkey_account(coldkey, swap_cost)?; + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); + + // 6. Burn the tokens + Self::burn_tokens(actual_burn_amount); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // 7. Swap owner. + // Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey. + // Owner::::remove(old_hotkey); + Owner::::insert(new_hotkey, coldkey.clone()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // 8. Swap OwnedHotkeys. + // OwnedHotkeys( coldkey ) -> Vec -- the hotkeys that the coldkey owns. + let mut hotkeys = OwnedHotkeys::::get(coldkey); + // Add the new key if needed. + if !hotkeys.contains(new_hotkey) { + hotkeys.push(new_hotkey.clone()); + OwnedHotkeys::::insert(coldkey, hotkeys); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + } + + // 9. Perform the hotkey swap + Self::perform_hotkey_swap_on_one_subnet(old_hotkey, new_hotkey, &mut weight, netuid); + + // 10. Update the last transaction block for the coldkey + Self::set_last_tx_block(coldkey, block); + LastHotkeySwapOnNetuid::::insert(netuid, coldkey, block); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + + // 11. Emit an event for the hotkey swap + Self::deposit_event(Event::HotkeySwappedOnSubnet { + coldkey: coldkey.clone(), + old_hotkey: old_hotkey.clone(), + new_hotkey: new_hotkey.clone(), + netuid, + }); + + Ok(Some(weight).into()) + } + + // do hotkey swap public part for both swap all subnets and just swap one subnet + pub fn perform_hotkey_swap_on_one_subnet( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + netuid: u16, + ) { + // 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 ) -> 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)); + + // 3. Swap all subnet specific info. + + // 3.1 Remove the previous hotkey and insert the new hotkey from membership. + // IsNetworkMember( hotkey, netuid ) -> bool -- is the hotkey a subnet member. + let is_network_member: bool = IsNetworkMember::::get(old_hotkey, netuid); + IsNetworkMember::::remove(old_hotkey, netuid); + IsNetworkMember::::insert(new_hotkey, netuid, is_network_member); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // 3.2 Swap Uids + Keys. + // Keys( netuid, hotkey ) -> uid -- the uid the hotkey has in the network if it is a member. + // Uids( netuid, hotkey ) -> uid -- the uids that the hotkey has. + if is_network_member { + // 3.2.1 Swap the UIDS + if let Ok(old_uid) = Uids::::try_get(netuid, old_hotkey) { + Uids::::remove(netuid, old_hotkey); + Uids::::insert(netuid, new_hotkey, old_uid); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // 3.2.2 Swap the keys. + Keys::::insert(netuid, old_uid, new_hotkey.clone()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); } + } - // 10.4. Swap axons. - // Axons( netuid, hotkey ) -> axon -- the axon that the hotkey has. - if is_network_member { - if let Ok(old_axon_info) = Axons::::try_get(netuid, old_hotkey) { - Axons::::remove(netuid, old_hotkey); - Axons::::insert(netuid, new_hotkey, old_axon_info); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } + // 3.3 Swap Prometheus. + // Prometheus( netuid, hotkey ) -> prometheus -- the prometheus data that a hotkey has in the network. + if is_network_member { + if let Ok(old_prometheus_info) = Prometheus::::try_get(netuid, old_hotkey) { + Prometheus::::remove(netuid, old_hotkey); + Prometheus::::insert(netuid, new_hotkey, old_prometheus_info); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } + } - // 10.5 Swap WeightCommits - // WeightCommits( hotkey ) --> Vec -- the weight commits for the hotkey. - if is_network_member { - if let Ok(old_weight_commits) = WeightCommits::::try_get(netuid, old_hotkey) { - WeightCommits::::remove(netuid, old_hotkey); - WeightCommits::::insert(netuid, new_hotkey, old_weight_commits); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } + // 3.4. Swap axons. + // Axons( netuid, hotkey ) -> axon -- the axon that the hotkey has. + if is_network_member { + if let Ok(old_axon_info) = Axons::::try_get(netuid, old_hotkey) { + Axons::::remove(netuid, old_hotkey); + Axons::::insert(netuid, new_hotkey, old_axon_info); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } + } - // 10.6. Swap the subnet loaded emission. - // LoadedEmission( netuid ) --> Vec<(hotkey, u64)> -- the loaded emission for the subnet. - if is_network_member { - if let Some(mut old_loaded_emission) = LoadedEmission::::get(netuid) { - for emission in old_loaded_emission.iter_mut() { - if emission.0 == *old_hotkey { - emission.0 = new_hotkey.clone(); - } - } - LoadedEmission::::remove(netuid); - LoadedEmission::::insert(netuid, old_loaded_emission); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } + // 3.5 Swap WeightCommits + // WeightCommits( hotkey ) --> Vec -- the weight commits for the hotkey. + if is_network_member { + if let Ok(old_weight_commits) = WeightCommits::::try_get(netuid, old_hotkey) { + WeightCommits::::remove(netuid, old_hotkey); + WeightCommits::::insert(netuid, new_hotkey, old_weight_commits); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } + } - // 10.7. Swap neuron TLS certificates. - // NeuronCertificates( netuid, hotkey ) -> Vec -- the neuron certificate for the hotkey. - if is_network_member { - if let Ok(old_neuron_certificates) = - NeuronCertificates::::try_get(netuid, old_hotkey) - { - NeuronCertificates::::remove(netuid, old_hotkey); - NeuronCertificates::::insert(netuid, new_hotkey, old_neuron_certificates); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + // 3.6. Swap the subnet loaded emission. + // LoadedEmission( netuid ) --> Vec<(hotkey, u64)> -- the loaded emission for the subnet. + if is_network_member { + if let Some(mut old_loaded_emission) = LoadedEmission::::get(netuid) { + for emission in old_loaded_emission.iter_mut() { + if emission.0 == *old_hotkey { + emission.0 = new_hotkey.clone(); + } } + LoadedEmission::::remove(netuid); + LoadedEmission::::insert(netuid, old_loaded_emission); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } - }); - - // 11. Swap Alpha - // Alpha( hotkey, coldkey, netuid ) -> alpha - let old_alpha_values: Vec<((T::AccountId, u16), U64F64)> = - Alpha::::iter_prefix((old_hotkey,)).collect(); - // Clear the entire old prefix here. - let _ = Alpha::::clear_prefix((old_hotkey,), old_alpha_values.len() as u32, None); - 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)); - - // Insert the new alpha values. - for ((coldkey, netuid), alpha) in old_alpha_values { - let new_alpha = Alpha::::get((new_hotkey, &coldkey, netuid)); - Alpha::::insert( - (new_hotkey, &coldkey, netuid), - new_alpha.saturating_add(alpha), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 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.retain(|hk| *hk != *old_hotkey && *hk != *new_hotkey); - staking_hotkeys.push(new_hotkey.clone()); - StakingHotkeys::::insert(&coldkey, staking_hotkeys); - weight.saturating_accrue(T::DbWeight::get().writes(1)); + // 3.7. Swap neuron TLS certificates. + // NeuronCertificates( netuid, hotkey ) -> Vec -- the neuron certificate for the hotkey. + if is_network_member { + if let Ok(old_neuron_certificates) = + NeuronCertificates::::try_get(netuid, old_hotkey) + { + NeuronCertificates::::remove(netuid, old_hotkey); + NeuronCertificates::::insert(netuid, new_hotkey, old_neuron_certificates); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } } - - // 12. Swap ChildKeys. + // 4. Swap ChildKeys. // ChildKeys( parent, netuid ) --> Vec<(proportion,child)> -- the child keys of the parent. - for netuid in Self::get_all_subnet_netuids() { - // Get the children of the old hotkey for this subnet - let my_children: Vec<(u64, T::AccountId)> = ChildKeys::::get(old_hotkey, netuid); - // Remove the old hotkey's child entries - ChildKeys::::remove(old_hotkey, netuid); - // Insert the same child entries for the new hotkey - ChildKeys::::insert(new_hotkey, netuid, my_children.clone()); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - for (_, child_key_i) in my_children { - // For each child, update their parent list - let mut child_parents: Vec<(u64, T::AccountId)> = - ParentKeys::::get(child_key_i.clone(), netuid); - for parent in child_parents.iter_mut() { - // If the parent is the old hotkey, replace it with the new hotkey - if parent.1 == *old_hotkey { - parent.1 = new_hotkey.clone(); - } + let my_children: Vec<(u64, T::AccountId)> = ChildKeys::::get(old_hotkey, netuid); + // Remove the old hotkey's child entries + ChildKeys::::remove(old_hotkey, netuid); + // Insert the same child entries for the new hotkey + ChildKeys::::insert(new_hotkey, netuid, my_children.clone()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + for (_, child_key_i) in my_children { + // For each child, update their parent list + let mut child_parents: Vec<(u64, T::AccountId)> = + ParentKeys::::get(child_key_i.clone(), netuid); + for parent in child_parents.iter_mut() { + // If the parent is the old hotkey, replace it with the new hotkey + if parent.1 == *old_hotkey { + parent.1 = new_hotkey.clone(); } - // Update the child's parent list - ParentKeys::::insert(child_key_i, netuid, child_parents); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } + // Update the child's parent list + ParentKeys::::insert(child_key_i, netuid, child_parents); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } + // } - // 13. Swap ParentKeys. + // 5. Swap ParentKeys. // ParentKeys( child, netuid ) --> Vec<(proportion,parent)> -- the parent keys of the child. - for netuid in Self::get_all_subnet_netuids() { - // Get the parents of the old hotkey for this subnet - let parents: Vec<(u64, T::AccountId)> = ParentKeys::::get(old_hotkey, netuid); - // Remove the old hotkey's parent entries - ParentKeys::::remove(old_hotkey, netuid); - // Insert the same parent entries for the new hotkey - ParentKeys::::insert(new_hotkey, netuid, parents.clone()); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - for (_, parent_key_i) in parents { - // For each parent, update their children list - let mut parent_children: Vec<(u64, T::AccountId)> = - ChildKeys::::get(parent_key_i.clone(), netuid); - for child in parent_children.iter_mut() { - // If the child is the old hotkey, replace it with the new hotkey - if child.1 == *old_hotkey { - child.1 = new_hotkey.clone(); - } + let parents: Vec<(u64, T::AccountId)> = ParentKeys::::get(old_hotkey, netuid); + // Remove the old hotkey's parent entries + ParentKeys::::remove(old_hotkey, netuid); + // Insert the same parent entries for the new hotkey + ParentKeys::::insert(new_hotkey, netuid, parents.clone()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + for (_, parent_key_i) in parents { + // For each parent, update their children list + let mut parent_children: Vec<(u64, T::AccountId)> = + ChildKeys::::get(parent_key_i.clone(), netuid); + for child in parent_children.iter_mut() { + // If the child is the old hotkey, replace it with the new hotkey + if child.1 == *old_hotkey { + child.1 = new_hotkey.clone(); } - // Update the parent's children list - ChildKeys::::insert(parent_key_i, netuid, parent_children); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } + // Update the parent's children list + ChildKeys::::insert(parent_key_i, netuid, parent_children); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } - // 14. Swap PendingChildKeys. + // 6. Swap PendingChildKeys. // PendingChildKeys( netuid, parent ) --> Vec<(proportion,child), cool_down_block> - for netuid in Self::get_all_subnet_netuids() { + if PendingChildKeys::::contains_key(netuid, old_hotkey) { + let (children, cool_down_block) = PendingChildKeys::::get(netuid, old_hotkey); + PendingChildKeys::::remove(netuid, old_hotkey); + PendingChildKeys::::insert(netuid, new_hotkey, (children, cool_down_block)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + // Also check for others with our hotkey as a child + for (hotkey, (children, cool_down_block)) in PendingChildKeys::::iter_prefix(netuid) { weight.saturating_accrue(T::DbWeight::get().reads(1)); - if PendingChildKeys::::contains_key(netuid, old_hotkey) { - let (children, cool_down_block) = PendingChildKeys::::get(netuid, old_hotkey); - PendingChildKeys::::remove(netuid, old_hotkey); - PendingChildKeys::::insert(netuid, new_hotkey, (children, cool_down_block)); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } - // Also check for others with our hotkey as a child - for (hotkey, (children, cool_down_block)) in PendingChildKeys::::iter_prefix(netuid) + if let Some(potential_idx) = + children.iter().position(|(_, child)| *child == *old_hotkey) { - weight.saturating_accrue(T::DbWeight::get().reads(1)); + let mut new_children = children.clone(); + let entry_to_remove = new_children.remove(potential_idx); + new_children.push((entry_to_remove.0, new_hotkey.clone())); // Keep the proportion. - if let Some(potential_idx) = - children.iter().position(|(_, child)| *child == *old_hotkey) - { - let mut new_children = children.clone(); - let entry_to_remove = new_children.remove(potential_idx); - new_children.push((entry_to_remove.0, new_hotkey.clone())); // Keep the proportion. - - PendingChildKeys::::remove(netuid, hotkey.clone()); - PendingChildKeys::::insert(netuid, hotkey, (new_children, cool_down_block)); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } + PendingChildKeys::::remove(netuid, hotkey.clone()); + PendingChildKeys::::insert(netuid, hotkey, (new_children, cool_down_block)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } } - // 15. Swap SubnetOwnerHotkey + // 7. Swap SubnetOwnerHotkey // SubnetOwnerHotkey( netuid ) --> hotkey -- the hotkey that is the owner of the subnet. - for netuid in Self::get_all_subnet_netuids() { - if let Ok(old_subnet_owner_hotkey) = SubnetOwnerHotkey::::try_get(netuid) { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - if old_subnet_owner_hotkey == *old_hotkey { - SubnetOwnerHotkey::::insert(netuid, new_hotkey); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } + if let Ok(old_subnet_owner_hotkey) = SubnetOwnerHotkey::::try_get(netuid) { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if old_subnet_owner_hotkey == *old_hotkey { + SubnetOwnerHotkey::::insert(netuid, new_hotkey); + weight.saturating_accrue(T::DbWeight::get().writes(1)); } } - // 16. Swap dividend records - TotalHotkeyAlphaLastEpoch::::iter_prefix(old_hotkey) - .drain() - .for_each(|(netuid, old_alpha)| { - // 16.1 Swap TotalHotkeyAlphaLastEpoch - 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)); - - // 16.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)); - - // 16.3 Swap TaoDividendsPerSubnet - let old_hotkey_tao_dividends = TaoDividendsPerSubnet::::get(netuid, old_hotkey); - let new_hotkey_tao_dividends = TaoDividendsPerSubnet::::get(netuid, new_hotkey); - TaoDividendsPerSubnet::::remove(netuid, old_hotkey); - TaoDividendsPerSubnet::::insert( - netuid, - new_hotkey, - old_hotkey_tao_dividends.saturating_add(new_hotkey_tao_dividends), - ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - }); + // 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 + let old_hotkey_tao_dividends = TaoDividendsPerSubnet::::get(netuid, old_hotkey); + let new_hotkey_tao_dividends = TaoDividendsPerSubnet::::get(netuid, new_hotkey); + TaoDividendsPerSubnet::::remove(netuid, old_hotkey); + TaoDividendsPerSubnet::::insert( + netuid, + new_hotkey, + old_hotkey_tao_dividends.saturating_add(new_hotkey_tao_dividends), + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - // Return successful after swapping all the relevant terms. - Ok(()) - } + // 9. Swap Alpha + // Alpha( hotkey, coldkey, netuid ) -> alpha + let old_alpha_values: Vec<((T::AccountId, u16), 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)); - pub fn swap_senate_member( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) -> DispatchResult { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - if T::SenateMembers::is_member(old_hotkey) { - T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + // Insert the new alpha values. + for ((coldkey, netuid_alpha), alpha) in old_alpha_values { + if netuid == netuid_alpha { + 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)); + } + } } - Ok(()) } } diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index d5d302d5c1..dd2ad1d7c9 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -213,6 +213,9 @@ parameter_types! { pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks pub const DurationOfStartCall: u64 = 7 * 24 * 60 * 60 / 12; // Default as 7 days + pub const InitialKeySwapOnSubnetCost: u64 = 10_000_000; + pub const HotkeySwapOnSubnetInterval: u64 = 15; // 15 block, should be bigger than subnet number, then trigger clean up for all subnets + } // Configure collective pallet for council @@ -441,6 +444,8 @@ impl crate::Config for Test { type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall; + type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost; + type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; } pub struct OriginPrivilegeCmp; diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index 161749a923..60fafacc22 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -22,5 +22,6 @@ mod staking2; mod subnet; mod swap_coldkey; mod swap_hotkey; +mod swap_hotkey_with_subnet; mod uids; mod weights; diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index a82972c2f7..0d6b24d7c0 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -22,7 +22,7 @@ fn test_swap_owner() { let mut weight = Weight::zero(); Owner::::insert(old_hotkey, coldkey); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -44,7 +44,7 @@ fn test_swap_owned_hotkeys() { let mut weight = Weight::zero(); OwnedHotkeys::::insert(coldkey, vec![old_hotkey]); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -95,7 +95,7 @@ fn test_swap_total_hotkey_stake() { ); // Swap hotkey - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -125,9 +125,12 @@ fn test_swap_senate_members() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - // Assuming there's a way to add a member to the senate - // SenateMembers::add_member(&old_hotkey); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SenateMembers::add_member(RuntimeOrigin::root(), old_hotkey)); + let members = SenateMembers::members(); + assert!(members.contains(&old_hotkey)); + assert!(!members.contains(&new_hotkey)); + + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -135,8 +138,9 @@ fn test_swap_senate_members() { )); // Assert that the old_hotkey is no longer a member and new_hotkey is now a member - // assert!(!SenateMembers::is_member(&old_hotkey)); - // assert!(SenateMembers::is_member(&new_hotkey)); + let members = SenateMembers::members(); + assert!(!members.contains(&old_hotkey)); + assert!(members.contains(&new_hotkey)); }); } @@ -150,7 +154,7 @@ fn test_swap_delegates() { let mut weight = Weight::zero(); Delegates::::insert(old_hotkey, 100); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -174,7 +178,7 @@ fn test_swap_subnet_membership() { add_network(netuid, 1, 1); IsNetworkMember::::insert(old_hotkey, netuid, true); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -202,7 +206,7 @@ fn test_swap_uids_and_keys() { Uids::::insert(netuid, old_hotkey, uid); Keys::::insert(netuid, uid, old_hotkey); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -230,7 +234,7 @@ fn test_swap_prometheus() { IsNetworkMember::::insert(old_hotkey, netuid, true); Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -260,7 +264,7 @@ fn test_swap_axons() { IsNetworkMember::::insert(old_hotkey, netuid, true); Axons::::insert(netuid, old_hotkey, axon_info.clone()); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -287,7 +291,7 @@ fn test_swap_certificates() { IsNetworkMember::::insert(old_hotkey, netuid, true); NeuronCertificates::::insert(netuid, old_hotkey, certificate.clone()); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -320,7 +324,7 @@ fn test_swap_weight_commits() { IsNetworkMember::::insert(old_hotkey, netuid, true); WeightCommits::::insert(netuid, old_hotkey, weight_commits.clone()); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -354,7 +358,7 @@ fn test_swap_loaded_emission() { vec![(old_hotkey, server_emission, validator_emission)], ); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -376,13 +380,17 @@ fn test_swap_staking_hotkeys() { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); let coldkey = U256::from(3); + + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let mut weight = Weight::zero(); - let netuid = 1; StakingHotkeys::::insert(coldkey, vec![old_hotkey]); Alpha::::insert((old_hotkey, coldkey, netuid), U64F64::from_num(100)); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -437,7 +445,7 @@ fn test_swap_hotkey_with_multiple_coldkeys() { let stake1_before = SubtensorModule::get_total_stake_for_coldkey(&coldkey1); let stake2_before = SubtensorModule::get_total_stake_for_coldkey(&coldkey2); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey1, @@ -473,7 +481,7 @@ fn test_swap_hotkey_with_multiple_subnets() { IsNetworkMember::::insert(old_hotkey, netuid1, true); IsNetworkMember::::insert(old_hotkey, netuid2, true); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -506,6 +514,8 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { // Set up initial state StakingHotkeys::::insert(coldkey1, vec![old_hotkey]); StakingHotkeys::::insert(coldkey2, vec![old_hotkey, staker5]); + Alpha::::insert((old_hotkey, coldkey1, netuid), U64F64::from_num(100)); + Alpha::::insert((old_hotkey, coldkey2, netuid), U64F64::from_num(100)); SubtensorModule::create_account_if_non_existent(&coldkey1, &old_hotkey); SubtensorModule::add_balance_to_coldkey_account( @@ -529,7 +539,7 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { stake )); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey1, @@ -564,7 +574,7 @@ fn test_swap_hotkey_with_no_stake() { // Set up initial state with no stake Owner::::insert(old_hotkey, coldkey); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey, @@ -635,7 +645,7 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { let total_hk_stake = SubtensorModule::get_total_stake_for_hotkey(&old_hotkey); assert!(total_hk_stake > 0); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &old_hotkey, &new_hotkey, &coldkey1, @@ -742,7 +752,8 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { assert_ok!(SubtensorModule::do_swap_hotkey( <::RuntimeOrigin>::signed(coldkey), &old_hotkey, - &new_hotkey_1 + &new_hotkey_1, + None )); // Attempt to perform another swap immediately, which should fail due to rate limit @@ -750,7 +761,8 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { SubtensorModule::do_swap_hotkey( <::RuntimeOrigin>::signed(coldkey), &new_hotkey_1, - &new_hotkey_2 + &new_hotkey_2, + None ), Error::::HotKeySetTxRateLimitExceeded ); @@ -760,7 +772,8 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { assert_ok!(SubtensorModule::do_swap_hotkey( <::RuntimeOrigin>::signed(coldkey), &new_hotkey_1, - &new_hotkey_2 + &new_hotkey_2, + None )); }); } @@ -787,34 +800,14 @@ fn test_do_swap_hotkey_err_not_owner() { SubtensorModule::do_swap_hotkey( <::RuntimeOrigin>::signed(not_owner_coldkey), &old_hotkey, - &new_hotkey + &new_hotkey, + None ), Error::::NonAssociatedColdKey ); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_owner_success --exact --nocapture -#[test] -fn test_swap_owner_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let mut weight = Weight::zero(); - - // Initialize Owner for old_hotkey - Owner::::insert(old_hotkey, coldkey); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the swap - assert_eq!(Owner::::get(new_hotkey), coldkey); - assert!(!Owner::::contains_key(old_hotkey)); - }); -} - // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_owner_old_hotkey_not_exist --exact --nocapture #[test] fn test_swap_owner_old_hotkey_not_exist() { @@ -828,7 +821,12 @@ fn test_swap_owner_old_hotkey_not_exist() { assert!(!Owner::::contains_key(old_hotkey)); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap_on_all_subnets( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); // Verify the swap assert_eq!(Owner::::get(new_hotkey), coldkey); @@ -851,7 +849,12 @@ fn test_swap_owner_new_hotkey_already_exists() { Owner::::insert(new_hotkey, another_coldkey); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap_on_all_subnets( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); // Verify the swap assert_eq!(Owner::::get(new_hotkey), coldkey); @@ -859,28 +862,6 @@ fn test_swap_owner_new_hotkey_already_exists() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey::test_swap_delegates_success --exact --show-output -#[test] -fn test_swap_delegates_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let delegate_take = 10u16; - let mut weight = Weight::zero(); - - // Initialize Delegates for old_hotkey - Delegates::::insert(old_hotkey, delegate_take); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the swap - assert_eq!(Delegates::::get(new_hotkey), delegate_take); - assert!(!Delegates::::contains_key(old_hotkey)); - }); -} - // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_stake_success --exact --nocapture #[test] fn test_swap_stake_success() { @@ -904,7 +885,12 @@ fn test_swap_stake_success() { TaoDividendsPerSubnet::::insert(netuid, old_hotkey, amount); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap_on_all_subnets( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); // Verify the swap assert_eq!(TotalHotkeyAlpha::::get(old_hotkey, netuid), 0); @@ -953,9 +939,12 @@ fn test_swap_stake_old_hotkey_not_exist() { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); let coldkey = U256::from(3); + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let alpha_share = U64F64::from_num(1234); let mut weight = Weight::zero(); - let netuid = 1; // Initialize Stake for old_hotkey Alpha::::insert((old_hotkey, coldkey, netuid), alpha_share); @@ -964,7 +953,12 @@ fn test_swap_stake_old_hotkey_not_exist() { assert!(Alpha::::contains_key((old_hotkey, coldkey, netuid))); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap_on_all_subnets( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); // Verify that new_hotkey has the stake and old_hotkey does not assert!(Alpha::::contains_key((new_hotkey, coldkey, netuid))); @@ -986,7 +980,7 @@ fn test_swap_stake_old_hotkey_not_exist() { // TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); // // Perform the swap -// SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); +// SubtensorModule::perform_hotkey_swap_on_all_subnets(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // // Verify the swap // assert_eq!( @@ -1015,11 +1009,12 @@ fn test_swap_hotkey_error_cases() { // Test not enough balance let swap_cost = SubtensorModule::get_key_swap_cost(); - assert_noop!( + assert_err!( SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(coldkey), &old_hotkey, - &new_hotkey + &new_hotkey, + None ), Error::::NotEnoughBalanceToPaySwapHotKey ); @@ -1032,7 +1027,8 @@ fn test_swap_hotkey_error_cases() { SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(coldkey), &old_hotkey, - &old_hotkey + &old_hotkey, + None ), Error::::NewHotKeyIsSameWithOld ); @@ -1043,7 +1039,8 @@ fn test_swap_hotkey_error_cases() { SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(coldkey), &old_hotkey, - &new_hotkey + &new_hotkey, + None ), Error::::HotKeyAlreadyRegisteredInSubNet ); @@ -1054,7 +1051,8 @@ fn test_swap_hotkey_error_cases() { SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(wrong_coldkey), &old_hotkey, - &new_hotkey + &new_hotkey, + None ), Error::::NonAssociatedColdKey ); @@ -1063,7 +1061,8 @@ fn test_swap_hotkey_error_cases() { assert_ok!(SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(coldkey), &old_hotkey, - &new_hotkey + &new_hotkey, + None )); // Check balance after swap @@ -1087,7 +1086,12 @@ fn test_swap_child_keys() { ChildKeys::::insert(old_hotkey, netuid, children.clone()); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap_on_all_subnets( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); // Verify the swap assert_eq!(ChildKeys::::get(new_hotkey, netuid), children); @@ -1115,7 +1119,12 @@ fn test_swap_parent_keys() { ChildKeys::::insert(U256::from(5), netuid, vec![(200u64, old_hotkey)]); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap_on_all_subnets( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); // Verify ParentKeys swap assert_eq!(ParentKeys::::get(new_hotkey, netuid), parents); @@ -1154,7 +1163,12 @@ fn test_swap_multiple_subnets() { ChildKeys::::insert(old_hotkey, netuid2, children2.clone()); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap_on_all_subnets( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); // Verify the swap for both subnets assert_eq!(ChildKeys::::get(new_hotkey, netuid1), children1); @@ -1199,7 +1213,12 @@ fn test_swap_complex_parent_child_structure() { ); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap_on_all_subnets( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); // Verify ParentKeys swap assert_eq!( @@ -1259,7 +1278,7 @@ fn test_swap_parent_hotkey_childkey_maps() { // Swap let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &parent_old, &parent_new, &coldkey, @@ -1314,7 +1333,7 @@ fn test_swap_child_hotkey_childkey_maps() { // Swap let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_hotkey_swap( + assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets( &child_old, &child_new, &coldkey, @@ -1352,7 +1371,12 @@ fn test_swap_hotkey_is_sn_owner_hotkey() { assert_eq!(SubnetOwnerHotkey::::get(netuid), old_hotkey); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap_on_all_subnets( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); // Check for SubnetOwnerHotkey assert_eq!(SubnetOwnerHotkey::::get(netuid), new_hotkey); @@ -1366,7 +1390,8 @@ fn test_swap_hotkey_swap_rate_limits() { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); let coldkey = U256::from(3); - let mut weight = Weight::zero(); + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); let last_tx_block = 123; let delegate_take_block = 4567; @@ -1380,7 +1405,12 @@ fn test_swap_hotkey_swap_rate_limits() { LastTxBlockChildKeyTake::::insert(old_hotkey, child_key_take_block); // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + None + )); // Check for new hotkey assert_eq!(LastTxBlock::::get(new_hotkey), last_tx_block); diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs new file mode 100644 index 0000000000..583a8cf448 --- /dev/null +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -0,0 +1,1516 @@ +#![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] + +use approx::assert_abs_diff_eq; +use codec::Encode; +use frame_support::weights::Weight; +use frame_support::{assert_err, assert_noop, assert_ok}; +use frame_system::{Config, RawOrigin}; + +use super::mock::*; +use crate::*; +use sp_core::{Get, H256, U256}; +use sp_runtime::SaturatedConversion; +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() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + Owner::::insert(old_hotkey, coldkey); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert_eq!(Owner::::get(old_hotkey), coldkey); + assert_eq!(Owner::::get(new_hotkey), coldkey); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_owned_hotkeys --exact --nocapture +#[test] +fn test_swap_owned_hotkeys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + OwnedHotkeys::::insert(coldkey, vec![old_hotkey]); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + let hotkeys = OwnedHotkeys::::get(coldkey); + assert!(hotkeys.contains(&old_hotkey)); + assert!(hotkeys.contains(&new_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_total_hotkey_stake --exact --nocapture +#[test] +fn test_swap_total_hotkey_stake() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let amount = DefaultMinStake::::get() * 10; + + let fee = DefaultStakingFee::::get(); + + //add network + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + + // Give it some $$$ in his coldkey balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + // Add stake + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + old_hotkey, + netuid, + amount + )); + + // Check if stake has increased + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), + amount - fee, + epsilon = amount / 1000, + ); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), + 0, + epsilon = 1, + ); + + // Swap hotkey + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + // Verify that total hotkey stake swapped + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), + 0, + epsilon = 1, + ); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), + amount - fee, + epsilon = amount / 1000, + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_senate_members --exact --nocapture +#[test] +fn test_swap_senate_members() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + assert_ok!(SenateMembers::add_member(RuntimeOrigin::root(), old_hotkey)); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + let members = SenateMembers::members(); + assert!(members.contains(&old_hotkey)); + assert!(!members.contains(&new_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_delegates --exact --nocapture +#[test] +fn test_swap_delegates() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + Delegates::::insert(old_hotkey, 100); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert!(Delegates::::contains_key(old_hotkey)); + assert!(!Delegates::::contains_key(new_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_subnet_membership --exact --nocapture +#[test] +fn test_swap_subnet_membership() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + IsNetworkMember::::insert(old_hotkey, netuid, true); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); + assert!(IsNetworkMember::::get(new_hotkey, netuid)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_uids_and_keys --exact --nocapture +#[test] +fn test_swap_uids_and_keys() { + new_test_ext(1).execute_with(|| { + let uid = 5u16; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + IsNetworkMember::::insert(old_hotkey, netuid, true); + Uids::::insert(netuid, old_hotkey, uid); + Keys::::insert(netuid, uid, old_hotkey); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert_eq!(Uids::::get(netuid, old_hotkey), None); + assert_eq!(Uids::::get(netuid, new_hotkey), Some(uid)); + assert_eq!(Keys::::get(netuid, uid), new_hotkey); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_prometheus --exact --nocapture +#[test] +fn test_swap_prometheus() { + new_test_ext(1).execute_with(|| { + let uid = 5u16; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let prometheus_info = PrometheusInfo::default(); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + IsNetworkMember::::insert(old_hotkey, netuid, true); + Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert!(!Prometheus::::contains_key(netuid, old_hotkey)); + assert_eq!( + Prometheus::::get(netuid, new_hotkey), + Some(prometheus_info) + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_axons --exact --nocapture +#[test] +fn test_swap_axons() { + new_test_ext(1).execute_with(|| { + let uid = 5u16; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let axon_info = AxonInfo::default(); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + IsNetworkMember::::insert(old_hotkey, netuid, true); + Axons::::insert(netuid, old_hotkey, axon_info.clone()); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert!(!Axons::::contains_key(netuid, old_hotkey)); + assert_eq!(Axons::::get(netuid, new_hotkey), Some(axon_info)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_certificates --exact --nocapture +#[test] +fn test_swap_certificates() { + new_test_ext(1).execute_with(|| { + let uid = 5u16; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let certificate = NeuronCertificate::try_from(vec![1, 2, 3]).unwrap(); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + IsNetworkMember::::insert(old_hotkey, netuid, true); + NeuronCertificates::::insert(netuid, old_hotkey, certificate.clone()); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert!(!NeuronCertificates::::contains_key( + netuid, old_hotkey + )); + assert_eq!( + NeuronCertificates::::get(netuid, new_hotkey), + Some(certificate) + ); + }); +} +use sp_std::collections::vec_deque::VecDeque; +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_weight_commits --exact --nocapture +#[test] +fn test_swap_weight_commits() { + new_test_ext(1).execute_with(|| { + let uid = 5u16; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let mut weight_commits: VecDeque<(H256, u64, u64, u64)> = VecDeque::new(); + weight_commits.push_back((H256::from_low_u64_be(100), 200, 1, 1)); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + IsNetworkMember::::insert(old_hotkey, netuid, true); + WeightCommits::::insert(netuid, old_hotkey, weight_commits.clone()); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert!(!WeightCommits::::contains_key(netuid, old_hotkey)); + assert_eq!( + WeightCommits::::get(netuid, new_hotkey), + Some(weight_commits) + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_loaded_emission --exact --nocapture +#[test] +fn test_swap_loaded_emission() { + new_test_ext(1).execute_with(|| { + let uid = 5u16; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let server_emission = 1000u64; + let validator_emission = 1000u64; + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + IsNetworkMember::::insert(old_hotkey, netuid, true); + LoadedEmission::::insert( + netuid, + vec![(old_hotkey, server_emission, validator_emission)], + ); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + let new_loaded_emission = LoadedEmission::::get(netuid); + assert_eq!( + new_loaded_emission, + Some(vec![(new_hotkey, server_emission, validator_emission)]) + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_staking_hotkeys --exact --nocapture +#[test] +fn test_swap_staking_hotkeys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + StakingHotkeys::::insert(coldkey, vec![old_hotkey]); + Alpha::::insert((old_hotkey, coldkey, netuid), U64F64::from_num(100)); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + let staking_hotkeys = StakingHotkeys::::get(coldkey); + assert!(staking_hotkeys.contains(&old_hotkey)); + assert!(staking_hotkeys.contains(&new_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey::test_swap_hotkey_with_multiple_coldkeys --exact --show-output --nocapture +#[test] +fn test_swap_hotkey_with_multiple_coldkeys() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey1 = U256::from(3); + let coldkey2 = U256::from(4); + + let stake = 1_000_000_000; + + StakingHotkeys::::insert(coldkey1, vec![old_hotkey]); + StakingHotkeys::::insert(coldkey2, vec![old_hotkey]); + SubtensorModule::create_account_if_non_existent(&coldkey1, &old_hotkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey1, u64::MAX); + SubtensorModule::add_balance_to_coldkey_account(&coldkey2, u64::MAX); + + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey1), + old_hotkey, + netuid, + stake + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey2), + old_hotkey, + netuid, + stake / 2 + )); + let stake1_before = SubtensorModule::get_total_stake_for_coldkey(&coldkey1); + let stake2_before = SubtensorModule::get_total_stake_for_coldkey(&coldkey2); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey1), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&coldkey1), + SubtensorModule::get_total_stake_for_coldkey(&coldkey1), + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&coldkey2), + SubtensorModule::get_total_stake_for_coldkey(&coldkey2), + ); + + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&coldkey1), + stake1_before + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&coldkey2), + stake2_before + ); + + assert!(StakingHotkeys::::get(coldkey1).contains(&new_hotkey)); + assert!(StakingHotkeys::::get(coldkey2).contains(&new_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_hotkey_with_multiple_subnets --exact --nocapture +#[test] +fn test_swap_hotkey_with_multiple_subnets() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + let netuid1 = add_dynamic_network(&old_hotkey, &coldkey); + let netuid2 = add_dynamic_network(&old_hotkey, &coldkey); + + IsNetworkMember::::insert(old_hotkey, netuid1, true); + IsNetworkMember::::insert(old_hotkey, netuid2, true); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid1) + )); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid2) + )); + + assert!(IsNetworkMember::::get(new_hotkey, netuid1)); + assert!(IsNetworkMember::::get(new_hotkey, netuid2)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid1)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid2)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_staking_hotkeys_multiple_coldkeys --exact --nocapture +#[test] +fn test_swap_staking_hotkeys_multiple_coldkeys() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey1 = U256::from(3); + let coldkey2 = U256::from(4); + let staker5 = U256::from(5); + + let stake = 1_000_000_000; + SubtensorModule::add_balance_to_coldkey_account(&coldkey1, u64::MAX); + SubtensorModule::add_balance_to_coldkey_account(&coldkey2, u64::MAX); + + // Set up initial state + StakingHotkeys::::insert(coldkey1, vec![old_hotkey]); + StakingHotkeys::::insert(coldkey2, vec![old_hotkey, staker5]); + + SubtensorModule::create_account_if_non_existent(&coldkey1, &old_hotkey); + + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey1), + old_hotkey, + netuid, + stake + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey2), + old_hotkey, + netuid, + stake + )); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey1), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + // Check if new_hotkey replaced old_hotkey in StakingHotkeys + assert!(StakingHotkeys::::get(coldkey1).contains(&new_hotkey)); + assert!(StakingHotkeys::::get(coldkey1).contains(&old_hotkey)); + + // Check if new_hotkey replaced old_hotkey for coldkey2 as well + assert!(StakingHotkeys::::get(coldkey2).contains(&new_hotkey)); + assert!(StakingHotkeys::::get(coldkey2).contains(&old_hotkey)); + assert!(StakingHotkeys::::get(coldkey2).contains(&staker5)); + // Other hotkeys should remain + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_hotkey_with_no_stake --exact --nocapture +#[test] +fn test_swap_hotkey_with_no_stake() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + // Set up initial state with no stake + Owner::::insert(old_hotkey, coldkey); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + // Check if ownership transferred + assert!(Owner::::contains_key(old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey); + + // Ensure no unexpected changes in Stake + assert!(!Alpha::::contains_key((old_hotkey, coldkey, netuid))); + assert!(!Alpha::::contains_key((new_hotkey, coldkey, netuid))); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_hotkey::test_swap_hotkey_with_multiple_coldkeys_and_subnets --exact --show-output +#[test] +fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey1 = U256::from(3); + let coldkey2 = U256::from(4); + let netuid1 = 1; + let netuid2 = 2; + let stake = DefaultMinStake::::get() * 10; + + // Set up initial state + add_network(netuid1, 1, 1); + add_network(netuid2, 1, 1); + register_ok_neuron(netuid1, old_hotkey, coldkey1, 1234); + register_ok_neuron(netuid2, old_hotkey, coldkey1, 1234); + + // Add balance to both coldkeys + SubtensorModule::add_balance_to_coldkey_account(&coldkey1, u64::MAX); + SubtensorModule::add_balance_to_coldkey_account(&coldkey2, u64::MAX); + + // Stake with coldkey1 + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey1), + old_hotkey, + netuid1, + stake + )); + + // Stake with coldkey2 also + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey2), + old_hotkey, + netuid2, + stake + )); + + let ck1_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &coldkey1, + netuid1, + ); + let ck2_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &coldkey2, + netuid2, + ); + assert!(ck1_stake > 0); + assert!(ck2_stake > 0); + let total_hk_stake = SubtensorModule::get_total_stake_for_hotkey(&old_hotkey); + assert!(total_hk_stake > 0); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey1), + &old_hotkey, + &new_hotkey, + Some(netuid1) + )); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey1), + &old_hotkey, + &new_hotkey, + Some(netuid2) + )); + + // Check ownership transfer + assert_eq!( + SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkey), + coldkey1 + ); + assert!(!SubtensorModule::get_owned_hotkeys(&coldkey2).contains(&new_hotkey)); + + // Check stake transfer + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &coldkey1, + netuid1 + ), + ck1_stake + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &new_hotkey, + &coldkey2, + netuid2 + ), + ck2_stake + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &coldkey1, + netuid1 + ), + 0 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &old_hotkey, + &coldkey2, + netuid2 + ), + 0 + ); + + // Check subnet membership transfer + assert!(SubtensorModule::is_hotkey_registered_on_network( + netuid1, + &new_hotkey + )); + assert!(SubtensorModule::is_hotkey_registered_on_network( + netuid2, + &new_hotkey + )); + assert!(!SubtensorModule::is_hotkey_registered_on_network( + netuid1, + &old_hotkey + )); + assert!(!SubtensorModule::is_hotkey_registered_on_network( + netuid2, + &old_hotkey + )); + + // Check total stake transfer + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), + total_hk_stake + ); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), 0); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_hotkey_tx_rate_limit_exceeded --exact --nocapture +#[test] +fn test_swap_hotkey_tx_rate_limit_exceeded() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey_1 = U256::from(2); + let new_hotkey_2 = U256::from(4); + let coldkey = U256::from(3); + let swap_cost = 1_000_000_000u64 * 2; + + let tx_rate_limit = 1; + + // Get the current transaction rate limit + let current_tx_rate_limit = SubtensorModule::get_tx_rate_limit(); + log::info!("current_tx_rate_limit: {:?}", current_tx_rate_limit); + + // Set the transaction rate limit + SubtensorModule::set_tx_rate_limit(tx_rate_limit); + // assert the rate limit is set to 1000 blocks + assert_eq!(SubtensorModule::get_tx_rate_limit(), tx_rate_limit); + + // Setup initial state + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost); + + // Perform the first swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey_1, + Some(netuid) + ),); + + // Attempt to perform another swap immediately, which should fail due to rate limit + assert_err!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey_1, + Some(netuid) + ), + Error::::HotKeySetTxRateLimitExceeded + ); + + // move in time past the rate limit + step_block(1001); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &new_hotkey_1, + &new_hotkey_2, + None + )); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_do_swap_hotkey_err_not_owner --exact --nocapture +#[test] +fn test_do_swap_hotkey_err_not_owner() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let not_owner_coldkey = U256::from(4); + let swap_cost = 1_000_000_000u64; + + // Setup initial state + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(¬_owner_coldkey, swap_cost); + + // Attempt the swap with a non-owner coldkey + assert_err!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(not_owner_coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ), + Error::::NonAssociatedColdKey + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_owner_old_hotkey_not_exist --exact --nocapture +#[test] +fn test_swap_owner_old_hotkey_not_exist() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid = add_dynamic_network(&new_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + // Ensure old_hotkey does not exist + assert!(!Owner::::contains_key(old_hotkey)); + + // Perform the swap + assert_err!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ), + Error::::NonAssociatedColdKey + ); + + // Verify the swap + assert_eq!(Owner::::get(new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_owner_new_hotkey_already_exists --exact --nocapture +#[test] +fn test_swap_owner_new_hotkey_already_exists() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let another_coldkey = U256::from(4); + + let netuid = add_dynamic_network(&new_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + // Initialize Owner for old_hotkey and new_hotkey + Owner::::insert(old_hotkey, coldkey); + Owner::::insert(new_hotkey, another_coldkey); + + // Perform the swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_err!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + + // Verify the swap + assert_eq!(Owner::::get(old_hotkey), coldkey); + assert!(Owner::::contains_key(old_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_stake_success --exact --nocapture +#[test] +fn test_swap_stake_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + let amount = 10_000; + let shares = U64F64::from_num(123456); + + // Initialize staking variables for old_hotkey + TotalHotkeyAlpha::::insert(old_hotkey, netuid, amount); + TotalHotkeyAlphaLastEpoch::::insert(old_hotkey, netuid, amount * 2); + TotalHotkeyShares::::insert(old_hotkey, netuid, U64F64::from_num(shares)); + Alpha::::insert((old_hotkey, coldkey, netuid), U64F64::from_num(amount)); + AlphaDividendsPerSubnet::::insert(netuid, old_hotkey, amount); + TaoDividendsPerSubnet::::insert(netuid, old_hotkey, amount); + + // Perform the swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ),); + + // Verify the swap + assert_eq!(TotalHotkeyAlpha::::get(old_hotkey, netuid), 0); + assert_eq!(TotalHotkeyAlpha::::get(new_hotkey, netuid), amount); + assert_eq!( + TotalHotkeyAlphaLastEpoch::::get(old_hotkey, netuid), + 0 + ); + assert_eq!( + TotalHotkeyAlphaLastEpoch::::get(new_hotkey, netuid), + amount * 2 + ); + assert_eq!( + TotalHotkeyShares::::get(old_hotkey, netuid), + U64F64::from_num(0) + ); + assert_eq!( + TotalHotkeyShares::::get(new_hotkey, netuid), + U64F64::from_num(shares) + ); + assert_eq!( + Alpha::::get((old_hotkey, coldkey, netuid)), + U64F64::from_num(0) + ); + assert_eq!( + Alpha::::get((new_hotkey, coldkey, netuid)), + U64F64::from_num(amount) + ); + assert_eq!(AlphaDividendsPerSubnet::::get(netuid, old_hotkey), 0); + assert_eq!( + AlphaDividendsPerSubnet::::get(netuid, new_hotkey), + amount + ); + assert_eq!(TaoDividendsPerSubnet::::get(netuid, old_hotkey), 0); + assert_eq!( + TaoDividendsPerSubnet::::get(netuid, new_hotkey), + amount + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_hotkey_error_cases --exact --nocapture +#[test] +fn test_swap_hotkey_error_cases() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let wrong_coldkey = U256::from(4); + let netuid = add_dynamic_network(&old_hotkey, &coldkey); + + // Set up initial state + Owner::::insert(old_hotkey, coldkey); + TotalNetworks::::put(1); + LastTxBlock::::insert(coldkey, 0); + + // Test not enough balance + let swap_cost = SubtensorModule::get_key_swap_cost(); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_err!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ), + Error::::NotEnoughBalanceToPaySwapHotKey + ); + + let initial_balance = SubtensorModule::get_key_swap_cost() + 1000; + SubtensorModule::add_balance_to_coldkey_account(&coldkey, initial_balance); + + // Test new hotkey same as old + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_noop!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &old_hotkey, + Some(netuid) + ), + Error::::NewHotKeyIsSameWithOld + ); + + // Test new hotkey already registered + IsNetworkMember::::insert(new_hotkey, netuid, true); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_noop!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + IsNetworkMember::::remove(new_hotkey, netuid); + + // Test non-associated coldkey + assert_noop!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(wrong_coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ), + Error::::NonAssociatedColdKey + ); + + // Run the successful swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ),); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_child_keys --exact --nocapture +#[test] +fn test_swap_child_keys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + let children = vec![(100u64, U256::from(4)), (200u64, U256::from(5))]; + + // Initialize ChildKeys for old_hotkey + ChildKeys::::insert(old_hotkey, netuid, children.clone()); + + // Perform the swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ),); + + // Verify the swap + assert_eq!(ChildKeys::::get(new_hotkey, netuid), children); + assert!(ChildKeys::::get(old_hotkey, netuid).is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_parent_keys --exact --nocapture +#[test] +fn test_swap_parent_keys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + let parents = vec![(100u64, U256::from(4)), (200u64, U256::from(5))]; + + // Initialize ParentKeys for old_hotkey + ParentKeys::::insert(old_hotkey, netuid, parents.clone()); + + // Initialize ChildKeys for parent + ChildKeys::::insert(U256::from(4), netuid, vec![(100u64, old_hotkey)]); + ChildKeys::::insert(U256::from(5), netuid, vec![(200u64, old_hotkey)]); + + // Perform the swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ),); + + // Verify ParentKeys swap + assert_eq!(ParentKeys::::get(new_hotkey, netuid), parents); + assert!(ParentKeys::::get(old_hotkey, netuid).is_empty()); + + // Verify ChildKeys update for parents + assert_eq!( + ChildKeys::::get(U256::from(4), netuid), + vec![(100u64, new_hotkey)] + ); + assert_eq!( + ChildKeys::::get(U256::from(5), netuid), + vec![(200u64, new_hotkey)] + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_multiple_subnets --exact --nocapture +#[test] +fn test_swap_multiple_subnets() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid1 = add_dynamic_network(&old_hotkey, &coldkey); + let netuid2 = add_dynamic_network(&old_hotkey, &coldkey); + + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + let children1 = vec![(100u64, U256::from(4)), (200u64, U256::from(5))]; + let children2 = vec![(300u64, U256::from(6))]; + + // Initialize ChildKeys for old_hotkey in multiple subnets + ChildKeys::::insert(old_hotkey, netuid1, children1.clone()); + ChildKeys::::insert(old_hotkey, netuid2, children2.clone()); + + // Perform the swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid1) + ),); + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid2) + ),); + + // Verify the swap for both subnets + assert_eq!(ChildKeys::::get(new_hotkey, netuid1), children1); + assert_eq!(ChildKeys::::get(new_hotkey, netuid2), children2); + assert!(ChildKeys::::get(old_hotkey, netuid1).is_empty()); + assert!(ChildKeys::::get(old_hotkey, netuid2).is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_complex_parent_child_structure --exact --nocapture +#[test] +fn test_swap_complex_parent_child_structure() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + let parent1 = U256::from(4); + let parent2 = U256::from(5); + let child1 = U256::from(6); + let child2 = U256::from(7); + + // Set up complex parent-child structure + ParentKeys::::insert( + old_hotkey, + netuid, + vec![(100u64, parent1), (200u64, parent2)], + ); + ChildKeys::::insert(old_hotkey, netuid, vec![(300u64, child1), (400u64, child2)]); + ChildKeys::::insert( + parent1, + netuid, + vec![(100u64, old_hotkey), (500u64, U256::from(8))], + ); + ChildKeys::::insert( + parent2, + netuid, + vec![(200u64, old_hotkey), (600u64, U256::from(9))], + ); + + // Perform the swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ),); + + // Verify ParentKeys swap + assert_eq!( + ParentKeys::::get(new_hotkey, netuid), + vec![(100u64, parent1), (200u64, parent2)] + ); + assert!(ParentKeys::::get(old_hotkey, netuid).is_empty()); + + // Verify ChildKeys swap + assert_eq!( + ChildKeys::::get(new_hotkey, netuid), + vec![(300u64, child1), (400u64, child2)] + ); + assert!(ChildKeys::::get(old_hotkey, netuid).is_empty()); + + // Verify parent's ChildKeys update + assert_eq!( + ChildKeys::::get(parent1, netuid), + vec![(100u64, new_hotkey), (500u64, U256::from(8))] + ); + assert_eq!( + ChildKeys::::get(parent2, netuid), + vec![(200u64, new_hotkey), (600u64, U256::from(9))] + ); + }); +} + +#[test] +fn test_swap_parent_hotkey_childkey_maps() { + new_test_ext(1).execute_with(|| { + let parent_old = U256::from(1); + let coldkey = U256::from(2); + let child = U256::from(3); + let child_other = U256::from(4); + let parent_new = U256::from(4); + + let netuid = add_dynamic_network(&parent_old, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + SubtensorModule::create_account_if_non_existent(&coldkey, &parent_old); + + // Set child and verify state maps + mock_set_children(&coldkey, &parent_old, netuid, &[(u64::MAX, child)]); + // Wait rate limit + step_rate_limit(&TransactionType::SetChildren, netuid); + // Schedule some pending child keys. + mock_schedule_children(&coldkey, &parent_old, netuid, &[(u64::MAX, child_other)]); + + assert_eq!( + ParentKeys::::get(child, netuid), + vec![(u64::MAX, parent_old)] + ); + assert_eq!( + ChildKeys::::get(parent_old, netuid), + vec![(u64::MAX, child)] + ); + let existing_pending_child_keys = PendingChildKeys::::get(netuid, parent_old); + assert_eq!(existing_pending_child_keys.0, vec![(u64::MAX, child_other)]); + + // Swap + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &parent_old, + &parent_new, + Some(netuid) + ),); + + // Verify parent and child keys updates + assert_eq!( + ParentKeys::::get(child, netuid), + vec![(u64::MAX, parent_new)] + ); + assert_eq!( + ChildKeys::::get(parent_new, netuid), + vec![(u64::MAX, child)] + ); + assert_eq!( + PendingChildKeys::::get(netuid, parent_new), + existing_pending_child_keys // Entry under new hotkey. + ); + }) +} + +#[test] +fn test_swap_child_hotkey_childkey_maps() { + new_test_ext(1).execute_with(|| { + let parent = U256::from(1); + let coldkey = U256::from(2); + let child_old = U256::from(3); + let child_new = U256::from(4); + let netuid = add_dynamic_network(&child_old, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + SubtensorModule::create_account_if_non_existent(&coldkey, &child_old); + SubtensorModule::create_account_if_non_existent(&coldkey, &parent); + + // Set child and verify state maps + mock_set_children(&coldkey, &parent, netuid, &[(u64::MAX, child_old)]); + // Wait rate limit + step_rate_limit(&TransactionType::SetChildren, netuid); + // Schedule some pending child keys. + mock_schedule_children(&coldkey, &parent, netuid, &[(u64::MAX, child_old)]); + + assert_eq!( + ParentKeys::::get(child_old, netuid), + vec![(u64::MAX, parent)] + ); + assert_eq!( + ChildKeys::::get(parent, netuid), + vec![(u64::MAX, child_old)] + ); + let existing_pending_child_keys = PendingChildKeys::::get(netuid, parent); + assert_eq!(existing_pending_child_keys.0, vec![(u64::MAX, child_old)]); + + // Swap + + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &child_old, + &child_new, + Some(netuid) + ),); + + // Verify parent and child keys updates + assert_eq!( + ParentKeys::::get(child_new, netuid), + vec![(u64::MAX, parent)] + ); + assert_eq!( + ChildKeys::::get(parent, netuid), + vec![(u64::MAX, child_new)] + ); + assert_eq!( + PendingChildKeys::::get(netuid, parent), + (vec![(u64::MAX, child_new)], existing_pending_child_keys.1) // Same cooldown block. + ); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_hotkey_is_sn_owner_hotkey --exact --nocapture +#[test] +fn test_swap_hotkey_is_sn_owner_hotkey() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + // Create dynamic network + let netuid = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + // Check for SubnetOwnerHotkey + assert_eq!(SubnetOwnerHotkey::::get(netuid), old_hotkey); + + // Perform the swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ),); + + // Check for SubnetOwnerHotkey + assert_eq!(SubnetOwnerHotkey::::get(netuid), new_hotkey); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_hotkey_swap_rate_limits --exact --nocapture +#[test] +fn test_swap_hotkey_swap_rate_limits() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let last_tx_block = 123; + let delegate_take_block = 4567; + let child_key_take_block = 8910; + + let netuid = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + + // Set the last tx block for the old hotkey + LastTxBlock::::insert(old_hotkey, last_tx_block); + // Set the last delegate take block for the old hotkey + LastTxBlockDelegateTake::::insert(old_hotkey, delegate_take_block); + // Set last childkey take block for the old hotkey + LastTxBlockChildKeyTake::::insert(old_hotkey, child_key_take_block); + + // Perform the swap + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ),); + + // Check for new hotkey + assert_eq!(LastTxBlock::::get(new_hotkey), last_tx_block); + assert_eq!( + LastTxBlockDelegateTake::::get(new_hotkey), + delegate_take_block + ); + assert_eq!( + LastTxBlockChildKeyTake::::get(new_hotkey), + child_key_take_block + ); + }); +} + +#[test] +fn test_swap_owner_failed_interval_not_passed() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + Owner::::insert(old_hotkey, coldkey); + assert_err!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ), + Error::::HotKeySwapOnSubnetIntervalNotPassed, + ); + }); +} + +#[test] +fn test_swap_owner_check_swap_block_set() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + Owner::::insert(old_hotkey, coldkey); + let new_block_number = System::block_number() + HotkeySwapOnSubnetInterval::get(); + System::set_block_number(new_block_number); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert_eq!( + LastHotkeySwapOnNetuid::::get(netuid, coldkey), + new_block_number + ); + }); +} + +#[test] +fn test_swap_owner_check_swap_record_clean_up() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + + let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); + Owner::::insert(old_hotkey, coldkey); + let new_block_number = System::block_number() + HotkeySwapOnSubnetInterval::get(); + System::set_block_number(new_block_number); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + )); + + assert_eq!( + LastHotkeySwapOnNetuid::::get(netuid, coldkey), + new_block_number + ); + + step_block((HotkeySwapOnSubnetInterval::get() as u16 + netuid) * 2); + assert!(!LastHotkeySwapOnNetuid::::contains_key( + netuid, coldkey + )); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9bf932b298..b934269cb8 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1118,6 +1118,8 @@ parameter_types! { } else { 7 * 24 * 60 * 60 / 12 // 7 days }; + pub const SubtensorInitialKeySwapOnSubnetCost: u64 = 1_000_000; // 0.001 TAO + pub const HotkeySwapOnSubnetInterval : BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days } impl pallet_subtensor::Config for Runtime { @@ -1187,6 +1189,8 @@ impl pallet_subtensor::Config for Runtime { type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall; + type KeySwapOnSubnetCost = SubtensorInitialKeySwapOnSubnetCost; + type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; } use sp_runtime::BoundedVec;