|
| 1 | +use super::*; |
| 2 | +use crate::MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; |
| 3 | +use frame_support::traits::fungible::Mutate; |
| 4 | +use frame_support::traits::tokens::Preservation; |
| 5 | +use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; |
| 6 | +use sp_core::{Get, U256}; |
| 7 | + |
| 8 | +impl<T: Config> Pallet<T> { |
| 9 | + /// Swaps the hotkey of a coldkey account. |
| 10 | + /// |
| 11 | + /// # Arguments |
| 12 | + /// |
| 13 | + /// * `origin` - The origin of the transaction, and also the coldkey account. |
| 14 | + /// * `old_hotkey` - The old hotkey to be swapped. |
| 15 | + /// * `new_hotkey` - The new hotkey to replace the old one. |
| 16 | + /// |
| 17 | + /// # Returns |
| 18 | + /// |
| 19 | + /// * `DispatchResultWithPostInfo` - The result of the dispatch. |
| 20 | + /// |
| 21 | + /// # Errors |
| 22 | + /// |
| 23 | + /// * `NonAssociatedColdKey` - If the coldkey does not own the old hotkey. |
| 24 | + /// * `HotKeySetTxRateLimitExceeded` - If the transaction rate limit is exceeded. |
| 25 | + /// * `NewHotKeyIsSameWithOld` - If the new hotkey is the same as the old hotkey. |
| 26 | + /// * `HotKeyAlreadyRegisteredInSubNet` - If the new hotkey is already registered in the subnet. |
| 27 | + /// * `NotEnoughBalanceToPaySwapHotKey` - If there is not enough balance to pay for the swap. |
| 28 | + pub fn do_swap_hotkey( |
| 29 | + origin: T::RuntimeOrigin, |
| 30 | + old_hotkey: &T::AccountId, |
| 31 | + new_hotkey: &T::AccountId, |
| 32 | + ) -> DispatchResultWithPostInfo { |
| 33 | + // Ensure the origin is signed and get the coldkey |
| 34 | + let coldkey = ensure_signed(origin)?; |
| 35 | + |
| 36 | + // Check if the coldkey is in arbitration |
| 37 | + ensure!( |
| 38 | + !Self::coldkey_in_arbitration(&coldkey), |
| 39 | + Error::<T>::ColdkeyIsInArbitration |
| 40 | + ); |
| 41 | + |
| 42 | + // Initialize the weight for this operation |
| 43 | + let mut weight = T::DbWeight::get().reads(2); |
| 44 | + |
| 45 | + // Ensure the new hotkey is different from the old one |
| 46 | + ensure!(old_hotkey != new_hotkey, Error::<T>::NewHotKeyIsSameWithOld); |
| 47 | + // Ensure the new hotkey is not already registered on any network |
| 48 | + ensure!( |
| 49 | + !Self::is_hotkey_registered_on_any_network(new_hotkey), |
| 50 | + Error::<T>::HotKeyAlreadyRegisteredInSubNet |
| 51 | + ); |
| 52 | + |
| 53 | + // Update the weight for the checks above |
| 54 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 0)); |
| 55 | + // Ensure the coldkey owns the old hotkey |
| 56 | + ensure!( |
| 57 | + Self::coldkey_owns_hotkey(&coldkey, old_hotkey), |
| 58 | + Error::<T>::NonAssociatedColdKey |
| 59 | + ); |
| 60 | + |
| 61 | + // Get the current block number |
| 62 | + let block: u64 = Self::get_current_block_as_u64(); |
| 63 | + // Ensure the transaction rate limit is not exceeded |
| 64 | + ensure!( |
| 65 | + !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), |
| 66 | + Error::<T>::HotKeySetTxRateLimitExceeded |
| 67 | + ); |
| 68 | + |
| 69 | + // Update the weight for reading the total networks |
| 70 | + weight.saturating_accrue( |
| 71 | + T::DbWeight::get().reads((TotalNetworks::<T>::get().saturating_add(1u16)) as u64), |
| 72 | + ); |
| 73 | + |
| 74 | + // Get the cost for swapping the key |
| 75 | + let swap_cost = Self::get_key_swap_cost(); |
| 76 | + log::debug!("Swap cost: {:?}", swap_cost); |
| 77 | + |
| 78 | + // Ensure the coldkey has enough balance to pay for the swap |
| 79 | + ensure!( |
| 80 | + Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), |
| 81 | + Error::<T>::NotEnoughBalanceToPaySwapHotKey |
| 82 | + ); |
| 83 | + // Remove the swap cost from the coldkey's account |
| 84 | + let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; |
| 85 | + // Burn the tokens |
| 86 | + Self::burn_tokens(actual_burn_amount); |
| 87 | + |
| 88 | + // Perform the hotkey swap |
| 89 | + Self::perform_hotkey_swap(old_hotkey, new_hotkey, &coldkey, &mut weight); |
| 90 | + |
| 91 | + // Update the last transaction block for the coldkey |
| 92 | + Self::set_last_tx_block(&coldkey, block); |
| 93 | + weight.saturating_accrue(T::DbWeight::get().writes(1)); |
| 94 | + |
| 95 | + // Emit an event for the hotkey swap |
| 96 | + Self::deposit_event(Event::HotkeySwapped { |
| 97 | + coldkey, |
| 98 | + old_hotkey: old_hotkey.clone(), |
| 99 | + new_hotkey: new_hotkey.clone(), |
| 100 | + }); |
| 101 | + |
| 102 | + // Return the weight of the operation |
| 103 | + Ok(Some(weight).into()) |
| 104 | + } |
| 105 | + |
| 106 | + pub fn perform_hotkey_swap( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, coldkey: &T::AccountId, weight: &mut Weight ) { |
| 107 | + |
| 108 | + // 1. Swap owner. |
| 109 | + // Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey. |
| 110 | + Owner::<T>::remove(old_hotkey); |
| 111 | + Owner::<T>::insert(new_hotkey, coldkey.clone()); |
| 112 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); |
| 113 | + |
| 114 | + // 2. Swap OwnedHotkeys. |
| 115 | + // OwnedHotkeys( coldkey ) -> Vec<hotkey> -- the hotkeys that the coldkey owns. |
| 116 | + let mut hotkeys = OwnedHotkeys::<T>::get(coldkey); |
| 117 | + // Add the new key if needed. |
| 118 | + if !hotkeys.contains(new_hotkey) { |
| 119 | + hotkeys.push(new_hotkey.clone()); |
| 120 | + } |
| 121 | + // Remove the old key. |
| 122 | + hotkeys.retain(|hk| *hk != *old_hotkey); |
| 123 | + OwnedHotkeys::<T>::insert(coldkey, hotkeys); |
| 124 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); |
| 125 | + |
| 126 | + // 3. Swap total hotkey stake. |
| 127 | + // TotalHotkeyStake( hotkey ) -> stake -- the total stake that the hotkey has across all delegates. |
| 128 | + let old_total_hotkey_stake = TotalHotkeyStake::<T>::get( old_hotkey ); // Get the old total hotkey stake. |
| 129 | + let new_total_hotkey_stake = TotalHotkeyStake::<T>::get( new_hotkey ); // Get the new total hotkey stake. |
| 130 | + TotalHotkeyStake::<T>::remove( old_hotkey ); // Remove the old total hotkey stake. |
| 131 | + TotalHotkeyStake::<T>::insert( new_hotkey, old_total_hotkey_stake.saturating_add( new_total_hotkey_stake ) ); // Insert the new total hotkey stake via the addition. |
| 132 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); |
| 133 | + |
| 134 | + // Swap total hotkey stakes. |
| 135 | + // TotalHotkeyColdkeyStakesThisInterval( hotkey ) --> (u64: stakes, u64: block_number) |
| 136 | + let stake_tuples: Vec<(T::AccountId, (u64, u64))> = TotalHotkeyColdkeyStakesThisInterval::<T>::iter_prefix(old_hotkey).collect(); |
| 137 | + for (coldkey, stake_tup) in stake_tuples { |
| 138 | + // NOTE: You could use this to increase your allowed stake operations but this would cost. |
| 139 | + TotalHotkeyColdkeyStakesThisInterval::<T>::insert(new_hotkey, &coldkey, stake_tup); |
| 140 | + TotalHotkeyColdkeyStakesThisInterval::<T>::remove(old_hotkey, &coldkey); |
| 141 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 142 | + } |
| 143 | + |
| 144 | + // Swap LastTxBlock |
| 145 | + // LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey. |
| 146 | + let old_last_tx_block: u64 = LastTxBlock::<T>::get( old_hotkey ); |
| 147 | + LastTxBlock::<T>::remove( old_hotkey ); |
| 148 | + LastTxBlock::<T>::insert( new_hotkey, Self::get_current_block_as_u64() ); |
| 149 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 150 | + |
| 151 | + // Swap LastTxBlockDelegateTake |
| 152 | + // LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take. |
| 153 | + LastTxBlockDelegateTake::<T>::remove( old_hotkey ); |
| 154 | + LastTxBlockDelegateTake::<T>::insert( new_hotkey, Self::get_current_block_as_u64() ); |
| 155 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 156 | + |
| 157 | + // Swap Senate members. |
| 158 | + // Senate( hotkey ) --> ? |
| 159 | + if T::SenateMembers::is_member(old_hotkey) { |
| 160 | + T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; |
| 161 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 162 | + } |
| 163 | + |
| 164 | + // 4. Swap delegates. |
| 165 | + // Delegates( hotkey ) -> take value -- the hotkey delegate take value. |
| 166 | + let old_delegate_take = Delegates::<T>::get( old_hotkey ); |
| 167 | + Delegates::<T>::remove( old_hotkey ); // Remove the old delegate take. |
| 168 | + Delegates::<T>::insert( new_hotkey, old_delegate_take ); // Insert the new delegate take. |
| 169 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 170 | + |
| 171 | + // Swap all subnet specific info. |
| 172 | + let all_netuid: Vec<u16> = Self::get_all_subnet_netuids(); |
| 173 | + for netuid in all_netuids { |
| 174 | + // 7.1 Remove the previous hotkey and insert the new hotkey from membership. |
| 175 | + // IsNetworkMember( hotkey, netuid ) -> bool -- is the hotkey a subnet member. |
| 176 | + let is_network_member: bool = IsNetworkMember::<T>::get( old_hotkey, netuid ); |
| 177 | + IsNetworkMember::<T>::remove( old_hotkey, netuid ); |
| 178 | + IsNetworkMember::<T>::insert( new_hotkey, netuid, is_network_member ); |
| 179 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 180 | + |
| 181 | + // 7.2 Swap Uids + Keys. |
| 182 | + // Keys( netuid, hotkey ) -> uid -- the uid the hotkey has in the network if it is a member. |
| 183 | + // Uids( netuid, hotkey ) -> uid -- the uids that the hotkey has. |
| 184 | + if is_network_member { |
| 185 | + // 7.2.1 Swap the UIDS |
| 186 | + let old_uid: u16 = Uids::<T>::get(netuid, old_hotkey); |
| 187 | + Uids::<T>::remove(netuid, old_hotkey); |
| 188 | + Uids::<T>::insert(netuid, new_hotkey, old_uid); |
| 189 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 190 | + |
| 191 | + // 7.2.2 Swap the keys. |
| 192 | + Keys::<T>::insert(netuid, old_uid, new_hotkey.clone()); |
| 193 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); |
| 194 | + } |
| 195 | + |
| 196 | + // 7.3 Swap Prometheus. |
| 197 | + // Prometheus( netuid, hotkey ) -> prometheus -- the prometheus data that a hotkey has in the network. |
| 198 | + if is_network_member { |
| 199 | + let old_prometheus_info: PrometheusInfo = Prometheus::<T>::get(netuid, old_hotkey); |
| 200 | + Prometheus::<T>::remove(netuid, old_hotkey); |
| 201 | + Prometheus::<T>::insert(netuid, new_hotkey, old_prometheus_info); |
| 202 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 203 | + } |
| 204 | + |
| 205 | + // 7.4. Swap axons. |
| 206 | + // Axons( netuid, hotkey ) -> axon -- the axon that the hotkey has. |
| 207 | + if is_network_member { |
| 208 | + let old_axon_info: AxonInfo = Axons::<T>::get(netuid, old_hotkey); |
| 209 | + Axons::<T>::remove(netuid, old_hotkey); |
| 210 | + Axons::<T>::insert(netuid, new_hotkey, old_axon_info); |
| 211 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 212 | + } |
| 213 | + |
| 214 | + // 7.5 Swap WeightCommits |
| 215 | + // WeightCommits( hotkey ) --> Vec<u64> -- the weight commits for the hotkey. |
| 216 | + if is_network_member { |
| 217 | + if let Ok(old_weight_commits) = WeightCommits::<T>::try_get(old_hotkey) { |
| 218 | + WeightCommits::<T>::remove(old_hotkey); |
| 219 | + WeightCommits::<T>::insert(new_hotkey, old_weight_commits); |
| 220 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + // 7.5. Swap the subnet loaded emission. |
| 225 | + // LoadedEmission( netuid ) --> Vec<(hotkey, u64)> -- the loaded emission for the subnet. |
| 226 | + if is_network_member { |
| 227 | + if let Some(mut old_loaded_emission) = LoadedEmission::<T>::get(netuid) { |
| 228 | + for emission in old_loaded_emission.iter_mut() { |
| 229 | + if emission.0 == *old_hotkey { |
| 230 | + emission.0 = new_hotkey.clone(); |
| 231 | + } |
| 232 | + } |
| 233 | + LoadedEmission::<T>::remove(netuid); |
| 234 | + LoadedEmission::<T>::insert(netuid, old_loaded_emission); |
| 235 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + } |
| 240 | + |
| 241 | + // Swap Stake. |
| 242 | + // Stake( hotkey, coldkey ) -> stake -- the stake that the hotkey controls on behalf of the coldkey. |
| 243 | + let stakes: Vec<(T::AccountId, u64)> = Stake::<T>::iter_prefix(old_hotkey).collect(); |
| 244 | + // Clear the entire old prefix here. |
| 245 | + let _ = Stake::<T>::clear_prefix( old_hotkey, stakes.len() as u32, None ); |
| 246 | + // Iterate over all the staking rows and insert them into the new hotkey. |
| 247 | + for (coldkey, old_stake_amount) in stakes { |
| 248 | + weight.saturating_accrue(T::DbWeight::get().reads(1)); |
| 249 | + |
| 250 | + // Swap Stake value |
| 251 | + // Stake( hotkey, coldkey ) -> stake -- the stake that the hotkey controls on behalf of the coldkey. |
| 252 | + // Get the new stake value. |
| 253 | + let new_stake_value: u64 = Stake::<T>::get(new_hotkey, &coldkey); |
| 254 | + // Insert the new stake value. |
| 255 | + Stake::<T>::insert(new_hotkey, &coldkey, new_stake_value.saturating_add(old_stake_amount)); |
| 256 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); |
| 257 | + |
| 258 | + // Swap StakingHotkeys. |
| 259 | + // StakingHotkeys( coldkey ) --> Vec<hotkey> -- the hotkeys that the coldkey stakes. |
| 260 | + let mut staking_hotkeys = StakingHotkeys::<T>::get(&coldkey); |
| 261 | + staking_hotkeys.retain(|hk| *hk != *old_hotkey && *hk != *new_hotkey); |
| 262 | + staking_hotkeys.push(new_hotkey.clone()); |
| 263 | + StakingHotkeys::<T>::insert(coldkey.clone(), staking_hotkeys); |
| 264 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); |
| 265 | + } |
| 266 | + } |
| 267 | + |
| 268 | + pub fn swap_senate_member( |
| 269 | + old_hotkey: &T::AccountId, |
| 270 | + new_hotkey: &T::AccountId, |
| 271 | + weight: &mut Weight, |
| 272 | + ) -> DispatchResult { |
| 273 | + weight.saturating_accrue(T::DbWeight::get().reads(1)); |
| 274 | + if T::SenateMembers::is_member(old_hotkey) { |
| 275 | + T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; |
| 276 | + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); |
| 277 | + } |
| 278 | + Ok(()) |
| 279 | + } |
| 280 | +} |
0 commit comments