From f6ccfa35bdef919c74b7cad5361e100d91224786 Mon Sep 17 00:00:00 2001 From: Loom Agent Date: Mon, 13 Jul 2026 15:52:33 +0000 Subject: [PATCH 1/2] fix(transaction-fee): make swap_hotkey(v2) fees payable in alpha fees_in_alpha listed every alpha-decreasing call except swap_hotkey and swap_hotkey_v2, so those calls returned an empty alpha-fee vec and were forced to pay the fee in TAO - rejecting callers that hold only alpha stake on the subnet. Add the two variants, mirroring remove_stake when a single netuid is given and unstake_all (all subnets) when netuid is None. spec_version 429 -> 430. --- pallets/transaction-fee/src/lib.rs | 27 ++++++++++ pallets/transaction-fee/src/tests/mod.rs | 66 ++++++++++++++++++++++++ runtime/src/lib.rs | 2 +- 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/pallets/transaction-fee/src/lib.rs b/pallets/transaction-fee/src/lib.rs index fbbe2cd805..918d9cd319 100644 --- a/pallets/transaction-fee/src/lib.rs +++ b/pallets/transaction-fee/src/lib.rs @@ -305,6 +305,33 @@ impl SubtensorTxFeeHandler { origin_netuid, .. }) => alpha_vec.push((hotkey.clone(), *origin_netuid)), + Some(SubtensorCall::swap_hotkey { + hotkey, + new_hotkey: _, + netuid, + }) => match netuid { + Some(netuid) => alpha_vec.push((hotkey.clone(), *netuid)), + None => { + let netuids = OU::get_all_netuids_for_coldkey_and_hotkey(who, hotkey); + netuids + .into_iter() + .for_each(|netuid| alpha_vec.push((hotkey.clone(), netuid))); + } + }, + Some(SubtensorCall::swap_hotkey_v2 { + hotkey, + new_hotkey: _, + netuid, + keep_stake: _, + }) => match netuid { + Some(netuid) => alpha_vec.push((hotkey.clone(), *netuid)), + None => { + let netuids = OU::get_all_netuids_for_coldkey_and_hotkey(who, hotkey); + netuids + .into_iter() + .for_each(|netuid| alpha_vec.push((hotkey.clone(), netuid))); + } + }, Some(SubtensorCall::recycle_alpha { hotkey, amount: _, diff --git a/pallets/transaction-fee/src/tests/mod.rs b/pallets/transaction-fee/src/tests/mod.rs index f452634cfe..c4ee2a39dc 100644 --- a/pallets/transaction-fee/src/tests/mod.rs +++ b/pallets/transaction-fee/src/tests/mod.rs @@ -204,6 +204,72 @@ fn test_rejects_multi_subnet_alpha_fee_deduction() { assert_eq!(alpha_before_1, alpha_after_1); }); } +// cargo test --package subtensor-transaction-fee --lib -- tests::test_swap_hotkey_fees_alpha --exact --show-output +#[test] +fn test_swap_hotkey_fees_alpha() { + new_test_ext().execute_with(|| { + let sn = setup_subnets(2, 2); + let stake_amount = TAO; + setup_stake( + sn.subnets[0].netuid, + &sn.coldkey, + &sn.hotkeys[0], + stake_amount, + ); + setup_stake( + sn.subnets[1].netuid, + &sn.coldkey, + &sn.hotkeys[0], + stake_amount, + ); + + // swap_hotkey and swap_hotkey_v2 move alpha stake off the origin hotkey, + // so their fees must be eligible to be paid in alpha on every subnet that + // hotkey has stake. Before the fix `fees_in_alpha` returned an empty vec + // for these calls, forcing a TAO fee (and rejecting alpha-only callers). + + // netuid = None -> every subnet the origin hotkey has stake on (2 here). + let call_all = RuntimeCall::SubtensorModule(pallet_subtensor::Call::swap_hotkey { + hotkey: sn.hotkeys[0], + new_hotkey: sn.hotkeys[1], + netuid: None, + }); + let alpha_vec_all = + SubtensorTxFeeHandler::>::fees_in_alpha::( + &sn.coldkey, + &call_all, + ); + assert_eq!(alpha_vec_all.len(), 2); + + // netuid = Some(single) -> only that subnet. + let call_one = RuntimeCall::SubtensorModule(pallet_subtensor::Call::swap_hotkey { + hotkey: sn.hotkeys[0], + new_hotkey: sn.hotkeys[1], + netuid: Some(sn.subnets[0].netuid), + }); + let alpha_vec_one = + SubtensorTxFeeHandler::>::fees_in_alpha::( + &sn.coldkey, + &call_one, + ); + assert_eq!(alpha_vec_one.len(), 1); + + // swap_hotkey_v2 moves the same alpha and must be eligible too. + let call_v2 = RuntimeCall::SubtensorModule(pallet_subtensor::Call::swap_hotkey_v2 { + hotkey: sn.hotkeys[0], + new_hotkey: sn.hotkeys[1], + netuid: None, + keep_stake: false, + }); + let alpha_vec_v2 = + SubtensorTxFeeHandler::>::fees_in_alpha::( + &sn.coldkey, + &call_v2, + ); + assert_eq!(alpha_vec_v2.len(), 2); + }); +} + // cargo test --package subtensor-transaction-fee --lib -- tests::test_remove_stake_fees_alpha --exact --show-output #[test] fn test_remove_stake_fees_alpha() { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 017a71a855..399d60658c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -235,7 +235,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 429, + spec_version: 430, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From e4b243f77c4825b58f77217adbaa7c9055545791 Mon Sep 17 00:00:00 2001 From: Loom Agent Date: Mon, 13 Jul 2026 15:52:33 +0000 Subject: [PATCH 2/2] test(subtensor): allow deprecated storage in last-tx-block migration test Greens the workspace clippy -D warnings gate (LastTxBlockDelegateTake was deprecated by the typed-units work; the migration test must reference the deprecated storage to verify the migration). --- pallets/subtensor/src/tests/migration.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 8e81447fbc..85eb1290c8 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -1253,6 +1253,7 @@ fn test_per_u16_encodes_identically_to_u16() { assert_eq!(PerU16::zero().encode(), 0u16.encode()); } +#[allow(deprecated)] #[test] fn test_migrate_last_tx_block_delegate_take() { new_test_ext(1).execute_with(|| {