Skip to content

Commit 9fd36b8

Browse files
committed
fix: only transfer RootClaimable during all-subnet hotkey swap
perform_hotkey_swap_on_one_subnet unconditionally called transfer_root_claimable_for_new_hotkey, which wiped the entire RootClaimable BTreeMap from the old hotkey. For single-subnet swaps the old hotkey retains root stake on other subnets, so losing its accumulated rates puts it into a permanently overclaimed state where RootClaimed >> RootClaimable, yielding near-zero root dividends. Move the transfer_root_claimable_for_new_hotkey call into perform_hotkey_swap_on_all_subnets, where it belongs. Fixes #2515
1 parent 7a727dd commit 9fd36b8

2 files changed

Lines changed: 92 additions & 8 deletions

File tree

pallets/subtensor/src/swap/swap_hotkey.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ impl<T: Config> Pallet<T> {
215215
)?;
216216
}
217217

218+
// 5.1. Transfer root claimable rates (all subnets at once).
219+
// This must only happen for full swaps — single-subnet swaps leave root
220+
// stake on the old hotkey, so the old hotkey must keep its RootClaimable.
221+
Self::transfer_root_claimable_for_new_hotkey(old_hotkey, new_hotkey);
222+
218223
// 6. Swap LastTxBlock
219224
// LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey.
220225
Self::remove_last_tx_block(old_hotkey);
@@ -543,10 +548,7 @@ impl<T: Config> Pallet<T> {
543548
weight.saturating_accrue(T::DbWeight::get().reads(old_alpha_values.len() as u64));
544549
weight.saturating_accrue(T::DbWeight::get().writes(old_alpha_values.len() as u64));
545550

546-
// 9.1. Transfer root claimable
547-
Self::transfer_root_claimable_for_new_hotkey(old_hotkey, new_hotkey);
548-
549-
// 9.2. Insert the new alpha values.
551+
// 9.1. Insert the new alpha values.
550552
for ((coldkey, netuid_alpha), alpha) in old_alpha_values {
551553
if netuid == netuid_alpha {
552554
Self::transfer_root_claimed_for_new_keys(

pallets/subtensor/src/tests/claim_root.rs

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,11 +1295,93 @@ fn test_claim_root_with_swap_hotkey() {
12951295
RootClaimed::<Test>::get((netuid, &new_hotkey, &coldkey,))
12961296
);
12971297

1298-
assert!(!RootClaimable::<Test>::get(hotkey).contains_key(&netuid));
1298+
// After a single-subnet swap, RootClaimable stays on the old hotkey
1299+
// because the old hotkey still holds root stake on other subnets.
1300+
// Only perform_hotkey_swap_on_all_subnets transfers RootClaimable.
1301+
let old_claimable_after = RootClaimable::<Test>::get(hotkey);
1302+
assert!(
1303+
old_claimable_after.contains_key(&netuid),
1304+
"single-subnet swap must not wipe RootClaimable from the old hotkey"
1305+
);
1306+
assert!(
1307+
!RootClaimable::<Test>::get(new_hotkey).contains_key(&netuid),
1308+
"single-subnet swap must not move RootClaimable to the new hotkey"
1309+
);
1310+
});
1311+
}
12991312

1300-
let _new_claimable = *RootClaimable::<Test>::get(new_hotkey)
1301-
.get(&netuid)
1302-
.expect("claimable must exist at this point");
1313+
#[test]
1314+
fn test_claim_root_with_swap_hotkey_all_subnets() {
1315+
new_test_ext(1).execute_with(|| {
1316+
let owner_coldkey = U256::from(1001);
1317+
let hotkey = U256::from(1002);
1318+
let coldkey = U256::from(1003);
1319+
let netuid = add_dynamic_network(&hotkey, &owner_coldkey);
1320+
1321+
SubtensorModule::set_tao_weight(u64::MAX);
1322+
SubnetMechanism::<Test>::insert(netuid, 1);
1323+
1324+
let tao_reserve = TaoCurrency::from(50_000_000_000);
1325+
let alpha_in = AlphaCurrency::from(100_000_000_000);
1326+
SubnetTAO::<Test>::insert(netuid, tao_reserve);
1327+
SubnetAlphaIn::<Test>::insert(netuid, alpha_in);
1328+
1329+
let root_stake = 2_000_000u64;
1330+
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
1331+
&hotkey,
1332+
&coldkey,
1333+
NetUid::ROOT,
1334+
root_stake.into(),
1335+
);
1336+
1337+
let initial_total_hotkey_alpha = 10_000_000u64;
1338+
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
1339+
&hotkey,
1340+
&owner_coldkey,
1341+
netuid,
1342+
initial_total_hotkey_alpha.into(),
1343+
);
1344+
1345+
let pending_root_alpha = 1_000_000u64;
1346+
SubtensorModule::distribute_emission(
1347+
netuid,
1348+
AlphaCurrency::ZERO,
1349+
AlphaCurrency::ZERO,
1350+
pending_root_alpha.into(),
1351+
AlphaCurrency::ZERO,
1352+
);
1353+
1354+
assert_ok!(SubtensorModule::set_root_claim_type(
1355+
RuntimeOrigin::signed(coldkey),
1356+
RootClaimTypeEnum::Keep
1357+
));
1358+
assert_ok!(SubtensorModule::claim_root(
1359+
RuntimeOrigin::signed(coldkey),
1360+
BTreeSet::from([netuid])
1361+
));
1362+
1363+
let new_hotkey = U256::from(10030);
1364+
assert!(RootClaimable::<Test>::get(hotkey).contains_key(&netuid));
1365+
assert!(!RootClaimable::<Test>::get(new_hotkey).contains_key(&netuid));
1366+
1367+
// Swap on ALL subnets — RootClaimable should transfer
1368+
let mut weight = Weight::zero();
1369+
assert_ok!(SubtensorModule::perform_hotkey_swap_on_all_subnets(
1370+
&hotkey,
1371+
&new_hotkey,
1372+
&coldkey,
1373+
&mut weight,
1374+
false,
1375+
));
1376+
1377+
assert!(
1378+
!RootClaimable::<Test>::get(hotkey).contains_key(&netuid),
1379+
"all-subnet swap must clear RootClaimable from the old hotkey"
1380+
);
1381+
assert!(
1382+
RootClaimable::<Test>::get(new_hotkey).contains_key(&netuid),
1383+
"all-subnet swap must move RootClaimable to the new hotkey"
1384+
);
13031385
});
13041386
}
13051387

0 commit comments

Comments
 (0)