Skip to content

Commit 6ff7b3f

Browse files
Merge pull request #701 from opentensor/fix/swap_delegates
fix: hotkey swap delegates
2 parents 6843cc7 + cab0fc3 commit 6ff7b3f

File tree

3 files changed

+169
-6
lines changed

3 files changed

+169
-6
lines changed

pallets/subtensor/src/swap/swap_hotkey.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,12 @@ impl<T: Config> Pallet<T> {
200200

201201
// 8. Swap delegates.
202202
// Delegates( hotkey ) -> take value -- the hotkey delegate take value.
203-
let old_delegate_take = Delegates::<T>::get(old_hotkey);
204-
Delegates::<T>::remove(old_hotkey); // Remove the old delegate take.
205-
Delegates::<T>::insert(new_hotkey, old_delegate_take); // Insert the new delegate take.
206-
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
207-
203+
if Delegates::<T>::contains_key(old_hotkey) {
204+
let old_delegate_take = Delegates::<T>::get(old_hotkey);
205+
Delegates::<T>::remove(old_hotkey);
206+
Delegates::<T>::insert(new_hotkey, old_delegate_take);
207+
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
208+
}
208209
// 9. Swap all subnet specific info.
209210
let all_netuids: Vec<u16> = Self::get_all_subnet_netuids();
210211
for netuid in all_netuids {

pallets/subtensor/tests/swap_hotkey.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,3 +959,165 @@ fn test_swap_hotkey_error_cases() {
959959
assert_eq!(Balances::free_balance(coldkey), initial_balance - swap_cost);
960960
});
961961
}
962+
963+
// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_does_not_become_delegate --exact --nocapture
964+
#[test]
965+
fn test_swap_hotkey_does_not_become_delegate() {
966+
new_test_ext(1).execute_with(|| {
967+
let netuid: u16 = 1;
968+
let tempo: u16 = 13;
969+
let old_hotkey = U256::from(1);
970+
let new_hotkey = U256::from(2);
971+
let coldkey = U256::from(3);
972+
let swap_cost = 1_000_000_000u64 * 2;
973+
let delegate_take = 10u16;
974+
975+
// Setup initial state
976+
add_network(netuid, tempo, 0);
977+
register_ok_neuron(netuid, old_hotkey, coldkey, 0);
978+
SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost);
979+
980+
// Ensure old_hotkey is not a delegate
981+
assert!(!Delegates::<Test>::contains_key(old_hotkey));
982+
983+
// Perform the swap
984+
assert_ok!(SubtensorModule::do_swap_hotkey(
985+
<<Test as Config>::RuntimeOrigin>::signed(coldkey),
986+
&old_hotkey,
987+
&new_hotkey
988+
));
989+
990+
// Check that old_hotkey is still not a delegate
991+
assert!(!Delegates::<Test>::contains_key(old_hotkey));
992+
993+
// Check that new_hotkey is NOT a delegate either
994+
assert!(!Delegates::<Test>::contains_key(new_hotkey));
995+
});
996+
}
997+
998+
// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_with_delegate_and_stakes --exact --nocapture
999+
#[test]
1000+
fn test_swap_hotkey_with_delegate_and_stakes() {
1001+
new_test_ext(1).execute_with(|| {
1002+
let netuid: u16 = 1;
1003+
let tempo: u16 = 13;
1004+
let old_hotkey = U256::from(1);
1005+
let new_hotkey = U256::from(2);
1006+
let coldkey = U256::from(3);
1007+
let staker1 = U256::from(4);
1008+
let staker2 = U256::from(5);
1009+
let swap_cost = 1_000_000_000u64;
1010+
let delegate_take = 11_796;
1011+
let stake_amount1 = 100u64;
1012+
let stake_amount2 = 200u64;
1013+
1014+
// Setup initial state
1015+
add_network(netuid, tempo, 0);
1016+
register_ok_neuron(netuid, old_hotkey, coldkey, 0);
1017+
SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost);
1018+
SubtensorModule::add_balance_to_coldkey_account(&staker1, stake_amount1);
1019+
SubtensorModule::add_balance_to_coldkey_account(&staker2, stake_amount2);
1020+
1021+
// Make old_hotkey a delegate
1022+
assert_ok!(SubtensorModule::become_delegate(
1023+
RuntimeOrigin::signed(coldkey),
1024+
old_hotkey,
1025+
));
1026+
1027+
// Add stakes to the delegate
1028+
assert_ok!(SubtensorModule::add_stake(
1029+
RuntimeOrigin::signed(staker1),
1030+
old_hotkey,
1031+
stake_amount1
1032+
));
1033+
assert_ok!(SubtensorModule::add_stake(
1034+
RuntimeOrigin::signed(staker2),
1035+
old_hotkey,
1036+
stake_amount2
1037+
));
1038+
1039+
// Assert initial stake amounts
1040+
assert_eq!(
1041+
SubtensorModule::get_total_stake_for_hotkey(&old_hotkey),
1042+
stake_amount1 + stake_amount2 - (ExistentialDeposit::get() * 2)
1043+
);
1044+
1045+
// Print entire staking hotkeys map
1046+
log::info!(
1047+
"StakingHotkeys before swap: {:?}",
1048+
StakingHotkeys::<Test>::iter().collect::<Vec<_>>()
1049+
);
1050+
1051+
// Perform the swap
1052+
assert_ok!(SubtensorModule::do_swap_hotkey(
1053+
<<Test as Config>::RuntimeOrigin>::signed(coldkey),
1054+
&old_hotkey,
1055+
&new_hotkey
1056+
));
1057+
1058+
// Check that new_hotkey is now a delegate with the same take
1059+
assert_eq!(Delegates::<Test>::get(new_hotkey), delegate_take);
1060+
assert!(!Delegates::<Test>::contains_key(old_hotkey));
1061+
1062+
// Check that stakes have been transferred to the new hotkey
1063+
assert_eq!(
1064+
SubtensorModule::get_total_stake_for_hotkey(&new_hotkey),
1065+
stake_amount1 + stake_amount2 - (ExistentialDeposit::get() * 2)
1066+
);
1067+
1068+
assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), 0);
1069+
1070+
// Check that the total stake for the new hotkey is correct
1071+
assert_eq!(
1072+
TotalHotkeyStake::<Test>::get(new_hotkey),
1073+
stake_amount1 + stake_amount2 - (ExistentialDeposit::get() * 2)
1074+
);
1075+
assert!(!TotalHotkeyStake::<Test>::contains_key(old_hotkey));
1076+
1077+
// Print entire staking hotkeys map
1078+
log::info!(
1079+
"StakingHotkeys after swap: {:?}",
1080+
StakingHotkeys::<Test>::iter().collect::<Vec<_>>()
1081+
);
1082+
1083+
// Check that the staking hotkeys for the stakers have been updated
1084+
assert!(StakingHotkeys::<Test>::get(staker1).contains(&new_hotkey));
1085+
assert!(StakingHotkeys::<Test>::get(staker2).contains(&new_hotkey));
1086+
assert!(!StakingHotkeys::<Test>::get(staker1).contains(&old_hotkey));
1087+
assert!(!StakingHotkeys::<Test>::get(staker2).contains(&old_hotkey));
1088+
1089+
// Check staking hotkeys for new hotkey
1090+
// Retrieve all stakers associated with the new hotkey
1091+
let new_hotkey_stakers = StakingHotkeys::<Test>::iter()
1092+
// Iterate through all entries in the StakingHotkeys storage map
1093+
.filter(|(_, hotkeys)| hotkeys.contains(&new_hotkey))
1094+
// Keep only entries where the new_hotkey is in the list of hotkeys
1095+
.map(|(staker, _)| staker)
1096+
// Extract just the staker (coldkey) from each matching entry
1097+
.collect::<Vec<_>>();
1098+
// Collect the results into a vector
1099+
1100+
log::info!("new_hotkey_stakers: {:?}", new_hotkey_stakers);
1101+
1102+
assert_eq!(new_hotkey_stakers.len(), 3);
1103+
assert!(new_hotkey_stakers.contains(&staker1));
1104+
assert!(new_hotkey_stakers.contains(&staker2));
1105+
1106+
// Verify that old_hotkey is not in any staker's StakingHotkeys
1107+
let old_hotkey_stakers = StakingHotkeys::<Test>::iter()
1108+
.filter(|(_, hotkeys)| hotkeys.contains(&old_hotkey))
1109+
.count();
1110+
1111+
assert_eq!(old_hotkey_stakers, 0);
1112+
1113+
// Check that the total balances of stakers haven't changed
1114+
assert_eq!(
1115+
SubtensorModule::get_coldkey_balance(&staker1),
1116+
ExistentialDeposit::get()
1117+
);
1118+
assert_eq!(
1119+
SubtensorModule::get_coldkey_balance(&staker2),
1120+
ExistentialDeposit::get()
1121+
);
1122+
});
1123+
}

runtime/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
139139
// `spec_version`, and `authoring_version` are the same between Wasm and native.
140140
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
141141
// the compatible custom types.
142-
spec_version: 191,
142+
spec_version: 193,
143143
impl_version: 1,
144144
apis: RUNTIME_API_VERSIONS,
145145
transaction_version: 1,

0 commit comments

Comments
 (0)