From b3c0900df6bf27db8f9b0c9fbdd6db2c6bd9ef39 Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 10 Sep 2025 14:01:35 +0800 Subject: [PATCH 01/19] set auto hotkey per subnet --- pallets/subtensor/rpc/src/lib.rs | 25 +++++++++++++++++++ pallets/subtensor/runtime-api/src/lib.rs | 1 + .../subtensor/src/coinbase/run_coinbase.rs | 2 +- pallets/subtensor/src/lib.rs | 13 +++++++--- pallets/subtensor/src/macros/dispatches.rs | 3 ++- pallets/subtensor/src/rpc_info/subnet_info.rs | 7 ++++++ pallets/subtensor/src/swap/swap_coldkey.rs | 11 ++++---- runtime/src/lib.rs | 4 +++ 8 files changed, 56 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index e3d5d8f1c1..ab60817495 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -83,6 +83,13 @@ pub trait SubtensorCustomApi { metagraph_index: Vec, at: Option, ) -> RpcResult>; + #[method(name = "subnetInfo_getColdkeyAutoStakeHotkey")] + fn get_coldkey_auto_stake_hotkey( + &self, + coldkey: AccountId32, + netuid: NetUid, + at: Option, + ) -> RpcResult>; } pub struct SubtensorCustom { @@ -427,4 +434,22 @@ where } } } + + fn get_coldkey_auto_stake_hotkey( + &self, + coldkey: AccountId32, + netuid: NetUid, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + match api.get_coldkey_auto_stake_hotkey(at, coldkey, netuid) { + Ok(result) => Ok(result.encode()), + Err(e) => Err(Error::RuntimeError(format!( + "Unable to get coldkey auto stake hotkey: {e:?}" + )) + .into()), + } + } } diff --git a/pallets/subtensor/runtime-api/src/lib.rs b/pallets/subtensor/runtime-api/src/lib.rs index 42d12eb686..27e62e1795 100644 --- a/pallets/subtensor/runtime-api/src/lib.rs +++ b/pallets/subtensor/runtime-api/src/lib.rs @@ -43,6 +43,7 @@ sp_api::decl_runtime_apis! { fn get_dynamic_info(netuid: NetUid) -> Option>; fn get_subnet_state(netuid: NetUid) -> Option>; fn get_selective_metagraph(netuid: NetUid, metagraph_indexes: Vec) -> Option>; + fn get_coldkey_auto_stake_hotkey(coldkey: AccountId32, netuid: NetUid) -> Option; } pub trait StakeInfoRuntimeApi { diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 7651a4162f..f908164220 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -512,7 +512,7 @@ impl Pallet { } let owner: T::AccountId = Owner::::get(&hotkey); - let maybe_dest = AutoStakeDestination::::get(&owner); + let maybe_dest = AutoStakeDestination::::get(&owner, netuid); // Always stake but only emit event if autostake is set. let destination = maybe_dest.clone().unwrap_or(hotkey.clone()); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index db4c385812..af8384436a 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1083,9 +1083,16 @@ pub mod pallet { #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns the vector of hotkeys controlled by this coldkey. pub type OwnedHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - #[pallet::storage] // --- MAP ( cold ) --> hot | Returns the hotkey a coldkey will autostake to with mining rewards. - pub type AutoStakeDestination = - StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, OptionQuery>; + #[pallet::storage] // --- DMAP ( cold, netuid )--> hot | Returns the hotkey a coldkey will autostake to with mining rewards. + pub type AutoStakeDestination = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Identity, + NetUid, + T::AccountId, + OptionQuery, + >; #[pallet::storage] // --- DMAP ( cold ) --> (block_expected, new_coldkey) | Maps coldkey to the block to swap at and new coldkey. pub type ColdkeySwapScheduled = StorageMap< diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index f069ff0aa5..c6fa468f38 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2025,11 +2025,12 @@ mod dispatches { .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] pub fn set_coldkey_auto_stake_hotkey( origin: T::RuntimeOrigin, + netuid: NetUid, hotkey: T::AccountId, ) -> DispatchResult { let coldkey = ensure_signed(origin)?; - AutoStakeDestination::::insert(coldkey, hotkey.clone()); + AutoStakeDestination::::insert(coldkey, netuid, hotkey.clone()); Ok(()) } diff --git a/pallets/subtensor/src/rpc_info/subnet_info.rs b/pallets/subtensor/src/rpc_info/subnet_info.rs index 11d60b2bc9..6a7966b4fb 100644 --- a/pallets/subtensor/src/rpc_info/subnet_info.rs +++ b/pallets/subtensor/src/rpc_info/subnet_info.rs @@ -403,4 +403,11 @@ impl Pallet { user_liquidity_enabled, }) } + + pub fn get_coldkey_auto_stake_hotkey( + coldkey: T::AccountId, + netuid: NetUid, + ) -> Option { + AutoStakeDestination::::get(coldkey, netuid) + } } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index f7f9997183..3c25a90294 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -155,6 +155,12 @@ impl Pallet { SubnetOwner::::insert(netuid, new_coldkey.clone()); } weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + if let Some(old_auto_stake_hotkey) = AutoStakeDestination::::get(old_coldkey, netuid) + { + AutoStakeDestination::::remove(old_coldkey, netuid); + AutoStakeDestination::::insert(new_coldkey, netuid, old_auto_stake_hotkey); + } } // 3. Swap Stake. @@ -178,11 +184,6 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } - if let Some(old_auto_stake_hotkey) = AutoStakeDestination::::get(old_coldkey) { - AutoStakeDestination::::remove(old_coldkey); - AutoStakeDestination::::insert(new_coldkey, old_auto_stake_hotkey); - } - // 4. Swap TotalColdkeyAlpha (DEPRECATED) // for netuid in Self::get_all_subnet_netuids() { // let old_alpha_stake: u64 = TotalColdkeyAlpha::::get(old_coldkey, netuid); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index aee1e04895..2bff005950 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2342,6 +2342,10 @@ impl_runtime_apis! { SubtensorModule::get_selective_metagraph(netuid, metagraph_indexes) } + fn get_coldkey_auto_stake_hotkey(coldkey: AccountId32, netuid: NetUid) -> Option { + SubtensorModule::get_coldkey_auto_stake_hotkey(coldkey, netuid) + } + } impl subtensor_custom_rpc_runtime_api::StakeInfoRuntimeApi for Runtime { From 9a6f521698005ec6000dc264ebbd9b2333c46cbc Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 10 Sep 2025 14:15:15 +0800 Subject: [PATCH 02/19] commit Cargo.lock --- pallets/subtensor/src/benchmarks.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 98307f5213..ed8a448765 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1560,9 +1560,10 @@ mod pallet_benchmarks { #[benchmark] fn set_coldkey_auto_stake_hotkey() { let coldkey: T::AccountId = whitelisted_caller(); + let netuid = NetUid::from(1); let hot: T::AccountId = account("A", 0, 1); #[extrinsic_call] - _(RawOrigin::Signed(coldkey.clone()), hot.clone()); + _(RawOrigin::Signed(coldkey.clone()), netuid, hot.clone()); } } From ce6e3d180606ca4800a8cb4a926c970eefdcd181 Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 10 Sep 2025 14:16:33 +0800 Subject: [PATCH 03/19] commit Cargo.lock --- pallets/subtensor/src/tests/coinbase.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 30cef8556f..79ea47afd7 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -2845,6 +2845,7 @@ fn test_incentive_is_autostaked_to_owner_destination() { // Set autostake destination for the miner's coldkey assert_ok!(SubtensorModule::set_coldkey_auto_stake_hotkey( RuntimeOrigin::signed(miner_ck), + netuid, dest_hk, )); From 342b1f576343f0c1425e6a5cadd5995986e822c7 Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 10 Sep 2025 14:19:44 +0800 Subject: [PATCH 04/19] bump version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2bff005950..ecaa168489 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,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: 315, + spec_version: 316, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From d96f53c73075e9f4a3209b2436ba759c3bf9035d Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 15 Sep 2025 20:28:55 +0800 Subject: [PATCH 05/19] commit Cargo.lock --- pallets/subtensor/rpc/src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index 5ed879eba0..b1901c5ce9 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -92,20 +92,19 @@ pub trait SubtensorCustomApi { metagraph_index: Vec, at: Option, ) -> RpcResult>; -<<<<<<< HEAD #[method(name = "subnetInfo_getColdkeyAutoStakeHotkey")] fn get_coldkey_auto_stake_hotkey( &self, coldkey: AccountId32, netuid: NetUid, -======= + at: Option, + ) -> RpcResult>; #[method(name = "subnetInfo_getSelectiveSubMetagraph")] fn get_selective_submetagraph( &self, netuid: NetUid, subid: SubId, metagraph_index: Vec, ->>>>>>> devnet-ready at: Option, ) -> RpcResult>; } @@ -483,7 +482,8 @@ where fn get_coldkey_auto_stake_hotkey( &self, coldkey: AccountId32, - netuid: NetUid,at: Option<::Hash>, + netuid: NetUid, + at: Option<::Hash>, ) -> RpcResult> { let api = self.client.runtime_api(); let at = at.unwrap_or_else(|| self.client.info().best_hash); @@ -507,7 +507,6 @@ where let api = self.client.runtime_api(); let at = at.unwrap_or_else(|| self.client.info().best_hash); - match api.get_selective_submetagraph(at, netuid, subid, metagraph_index) { Ok(result) => Ok(result.encode()), Err(e) => { From a02d6d4ce803b9c165fd17b433738780f1420e17 Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 18 Sep 2025 16:50:03 +0800 Subject: [PATCH 06/19] fix conflict --- pallets/subtensor/rpc/src/lib.rs | 17 ----------------- pallets/subtensor/runtime-api/src/lib.rs | 1 - runtime/src/lib.rs | 4 ---- 3 files changed, 22 deletions(-) diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index 51876d9f71..074e20943a 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -506,23 +506,6 @@ where } } - fn get_selective_submetagraph( - &self, - netuid: NetUid, - metagraph_index: Vec, - at: Option<::Hash>, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let at = at.unwrap_or_else(|| self.client.info().best_hash); - - match api.get_selective_mechagraph(at, netuid, metagraph_index) { - Ok(result) => Ok(result.encode()), - Err(e) => { - Err(Error::RuntimeError(format!("Unable to get selective metagraph: {e:?}")).into()) - } - } - } - fn get_selective_mechagraph( &self, netuid: NetUid, diff --git a/pallets/subtensor/runtime-api/src/lib.rs b/pallets/subtensor/runtime-api/src/lib.rs index b7104bb396..f1107bad08 100644 --- a/pallets/subtensor/runtime-api/src/lib.rs +++ b/pallets/subtensor/runtime-api/src/lib.rs @@ -46,7 +46,6 @@ sp_api::decl_runtime_apis! { fn get_subnet_state(netuid: NetUid) -> Option>; fn get_selective_metagraph(netuid: NetUid, metagraph_indexes: Vec) -> Option>; fn get_coldkey_auto_stake_hotkey(coldkey: AccountId32, netuid: NetUid) -> Option; - fn get_selective_submetagraph(netuid: NetUid, metagraph_indexes: Vec) -> Option>; fn get_selective_mechagraph(netuid: NetUid, subid: MechId, metagraph_indexes: Vec) -> Option>; fn get_subnet_to_prune() -> Option; } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 56a3c72053..c5a4bb68a7 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2375,10 +2375,6 @@ impl_runtime_apis! { SubtensorModule::get_coldkey_auto_stake_hotkey(coldkey, netuid) } - fn get_selective_submetagraph(netuid: NetUid, subid: SubId, metagraph_indexes: Vec) -> Option> { - SubtensorModule::get_selective_submetagraph(netuid, subid, metagraph_indexes) - } - fn get_selective_mechagraph(netuid: NetUid, mecid: MechId, metagraph_indexes: Vec) -> Option> { SubtensorModule::get_selective_mechagraph(netuid, mecid, metagraph_indexes) } From d9ac29ba55a2a801c8a810efe93606d335f92d9c Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 18 Sep 2025 16:55:03 +0800 Subject: [PATCH 07/19] commit Cargo.lock --- pallets/subtensor/rpc/src/lib.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index 074e20943a..b5749988ee 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -99,13 +99,6 @@ pub trait SubtensorCustomApi { netuid: NetUid, at: Option, ) -> RpcResult>; - #[method(name = "subnetInfo_getSelectiveSubMetagraph")] - fn get_selective_submetagraph( - &self, - netuid: NetUid, - metagraph_index: Vec, - at: Option, - ) -> RpcResult>; #[method(name = "subnetInfo_getSelectiveMechagraph")] fn get_selective_mechagraph( &self, From 8842136707ab48881b8fe1d179365b99ae8a62c0 Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 18 Sep 2025 17:47:28 +0800 Subject: [PATCH 08/19] bump version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index c5a4bb68a7..b179d68405 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,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: 317, + spec_version: 318, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 32ef9cc5bb9d0e21245a2553ead170b82d825af3 Mon Sep 17 00:00:00 2001 From: juniuszhou Date: Mon, 22 Sep 2025 05:45:57 +0100 Subject: [PATCH 09/19] add migration --- .../migrate_auto_stake_destination.rs | 83 ++++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/tests/migration.rs | 104 ++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 pallets/subtensor/src/migrations/migrate_auto_stake_destination.rs diff --git a/pallets/subtensor/src/migrations/migrate_auto_stake_destination.rs b/pallets/subtensor/src/migrations/migrate_auto_stake_destination.rs new file mode 100644 index 0000000000..6e910a6680 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_auto_stake_destination.rs @@ -0,0 +1,83 @@ +use super::*; +use crate::AccountIdOf; +use frame_support::{ + IterableStorageMap, + pallet_prelude::{Blake2_128Concat, OptionQuery}, + storage_alias, + traits::Get, + weights::Weight, +}; +use scale_info::prelude::string::String; + +/// Module containing deprecated storage format for AutoStakeDestination +pub mod deprecated_auto_stake_destination_format { + use super::*; + + #[storage_alias] + pub(super) type AutoStakeDestination = + StorageMap, Blake2_128Concat, AccountIdOf, AccountIdOf, OptionQuery>; +} + +/// Migrate the AutoStakeDestination map from single map to double map format +pub fn migrate_auto_stake_destination() -> Weight { + use deprecated_auto_stake_destination_format as old; + + let migration_name = b"migrate_auto_stake_destination".to_vec(); + let mut weight = T::DbWeight::get().reads(1); + + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + // ------------------------------ + // Step 1: Migrate AutoStakeDestination entries + // ------------------------------ + + let curr_keys: Vec> = old::AutoStakeDestination::::iter_keys().collect(); + let root_netuid = NetUid::ROOT; + let netuids: Vec = as IterableStorageMap>::iter() + .map(|(netuid, _)| netuid) + .collect(); + + // Process each old entry + for coldkey in &curr_keys { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + if let Some(hotkey) = old::AutoStakeDestination::::get(coldkey) { + for netuid in netuids.iter() { + if *netuid == root_netuid { + continue; + } + AutoStakeDestination::::insert(coldkey, netuid, hotkey.clone()); + } + + old::AutoStakeDestination::::remove(coldkey); + + weight.saturating_accrue(T::DbWeight::get().writes(netuids.len() as u64)); + } + } + + // ------------------------------ + // Step 2: Mark Migration as Completed + // ------------------------------ + + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed successfully. {} entries migrated.", + String::from_utf8_lossy(&migration_name), + curr_keys.len() + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index e7c50c0080..51c90c2e1e 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -24,6 +24,7 @@ pub mod migrate_network_lock_reduction_interval; pub mod migrate_orphaned_storage_items; pub mod migrate_populate_owned_hotkeys; pub mod migrate_rao; +pub mod migrate_auto_stake_destination; pub mod migrate_rate_limiting_last_blocks; pub mod migrate_remove_commitments_rate_limit; pub mod migrate_remove_network_modality; diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 816c87837e..b37c25f04c 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -1502,6 +1502,110 @@ fn test_migrate_commit_reveal_settings_values_access() { }); } +#[test] +fn test_migrate_auto_stake_destination() { + new_test_ext(1).execute_with(|| { + // ------------------------------ + // Step 1: Simulate Old Storage Entries + // ------------------------------ + const MIGRATION_NAME: &[u8] = b"migrate_auto_stake_destination"; + let netuids = [NetUid::ROOT, NetUid::from(1), NetUid::from(2), NetUid::from(42)]; + for netuid in &netuids { + NetworksAdded::::insert(*netuid, true); + } + + let pallet_prefix = twox_128("SubtensorModule".as_bytes()); + let storage_prefix = twox_128("AutoStakeDestination".as_bytes()); + + // Create test accounts + let coldkey1: U256 = U256::from(1); + let coldkey2: U256 = U256::from(2); + let hotkey1: U256 = U256::from(100); + let hotkey2: U256 = U256::from(200); + + // Construct storage keys for old format (StorageMap) + let mut key1 = Vec::new(); + key1.extend_from_slice(&pallet_prefix); + key1.extend_from_slice(&storage_prefix); + key1.extend_from_slice(&Blake2_128Concat::hash(&coldkey1.encode())); + + let mut key2 = Vec::new(); + key2.extend_from_slice(&pallet_prefix); + key2.extend_from_slice(&storage_prefix); + key2.extend_from_slice(&Blake2_128Concat::hash(&coldkey2.encode())); + + // Store old format entries + put_raw(&key1, &hotkey1.encode()); + put_raw(&key2, &hotkey2.encode()); + + // Verify old entries are stored + assert_eq!(get_raw(&key1), Some(hotkey1.encode())); + assert_eq!(get_raw(&key2), Some(hotkey2.encode())); + + assert!( + !HasMigrationRun::::get(MIGRATION_NAME.to_vec()), + "Migration should not have run yet" + ); + + // ------------------------------ + // Step 2: Run the Migration + // ------------------------------ + let weight = crate::migrations::migrate_auto_stake_destination::migrate_auto_stake_destination::(); + + assert!( + HasMigrationRun::::get(MIGRATION_NAME.to_vec()), + "Migration should be marked as run" + ); + + // ------------------------------ + // Step 3: Verify Migration Effects + // ------------------------------ + + // Verify new format entries exist + for netuid in &netuids { + if *netuid == NetUid::ROOT { + assert_eq!( + AutoStakeDestination::::get(coldkey1, NetUid::ROOT), + None + ); + assert_eq!( + AutoStakeDestination::::get(coldkey2, NetUid::ROOT), + None + ); + } else { + assert_eq!( + AutoStakeDestination::::get(coldkey1, *netuid), + Some(hotkey1) + ); + assert_eq!( + AutoStakeDestination::::get(coldkey2, *netuid), + Some(hotkey2) + ); + } + + } + + // Verify old format entries are cleared + assert_eq!(get_raw(&key1), None, "Old storage entry 1 should be cleared"); + assert_eq!(get_raw(&key2), None, "Old storage entry 2 should be cleared"); + + // Verify weight calculation + assert!(!weight.is_zero(), "Migration weight should be non-zero"); + + // ------------------------------ + // Step 4: Test Migration Idempotency + // ------------------------------ + let weight_second_run = crate::migrations::migrate_auto_stake_destination::migrate_auto_stake_destination::(); + + // Second run should only read the migration flag + assert_eq!( + weight_second_run, + ::DbWeight::get().reads(1), + "Second run should only read the migration flag" + ); + }); +} + #[test] fn test_migrate_crv3_v2_to_timelocked() { new_test_ext(1).execute_with(|| { From 9733f167fd8205d808b5044b493f5e2e87482226 Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 22 Sep 2025 05:48:05 +0100 Subject: [PATCH 10/19] fix comments --- .../subtensor/src/migrations/migrate_auto_stake_destination.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/src/migrations/migrate_auto_stake_destination.rs b/pallets/subtensor/src/migrations/migrate_auto_stake_destination.rs index 6e910a6680..13f165db40 100644 --- a/pallets/subtensor/src/migrations/migrate_auto_stake_destination.rs +++ b/pallets/subtensor/src/migrations/migrate_auto_stake_destination.rs @@ -48,7 +48,6 @@ pub fn migrate_auto_stake_destination() -> Weight { .map(|(netuid, _)| netuid) .collect(); - // Process each old entry for coldkey in &curr_keys { weight.saturating_accrue(T::DbWeight::get().reads(1)); From 5cfa430984e010f4f4d08ef1329ecd7eb2974620 Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 22 Sep 2025 05:55:17 +0100 Subject: [PATCH 11/19] bump version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b179d68405..97d558a30a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,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: 318, + spec_version: 319, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 8414776037a5eda9b37ef862f3ec5d6b5c555b9d Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 22 Sep 2025 07:32:07 +0100 Subject: [PATCH 12/19] cargo fmt --- pallets/subtensor/src/migrations/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index 51c90c2e1e..ef2df8bdec 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -4,6 +4,7 @@ use frame_support::pallet_prelude::Weight; use sp_io::KillStorageResult; use sp_io::hashing::twox_128; use sp_io::storage::clear_prefix; +pub mod migrate_auto_stake_destination; pub mod migrate_chain_identity; pub mod migrate_coldkey_swap_scheduled; pub mod migrate_commit_reveal_settings; @@ -24,7 +25,6 @@ pub mod migrate_network_lock_reduction_interval; pub mod migrate_orphaned_storage_items; pub mod migrate_populate_owned_hotkeys; pub mod migrate_rao; -pub mod migrate_auto_stake_destination; pub mod migrate_rate_limiting_last_blocks; pub mod migrate_remove_commitments_rate_limit; pub mod migrate_remove_network_modality; From 6c7caffb814bd6e498838e9bda1b374e4aec8f0c Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 29 Sep 2025 20:03:51 +0800 Subject: [PATCH 13/19] add all check and unit tests --- pallets/subtensor/src/macros/dispatches.rs | 21 +++- pallets/subtensor/src/macros/errors.rs | 2 + pallets/subtensor/src/macros/events.rs | 14 +++ .../subtensor/src/tests/auto_stake_hotkey.rs | 112 ++++++++++++++++++ pallets/subtensor/src/tests/coinbase.rs | 1 + pallets/subtensor/src/tests/mod.rs | 1 + 6 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/tests/auto_stake_hotkey.rs diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 2e5e0a62b8..381cde2605 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2301,8 +2301,27 @@ mod dispatches { hotkey: T::AccountId, ) -> DispatchResult { let coldkey = ensure_signed(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + ensure!( + Uids::::contains_key(netuid, &hotkey), + Error::::HotKeyNotRegisteredInSubNet + ); + + let current_hotkey = AutoStakeDestination::::get(coldkey.clone(), netuid); + if let Some(current_hotkey) = current_hotkey { + ensure!( + current_hotkey != hotkey, + Error::::SameAutoStakeDestinationAlreadySet + ); + } - AutoStakeDestination::::insert(coldkey, netuid, hotkey.clone()); + AutoStakeDestination::::insert(coldkey.clone(), netuid, hotkey.clone()); + + Self::deposit_event(Event::AutoStakeDestinationSet { + coldkey, + netuid, + hotkey, + }); Ok(()) } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 98c1742e9a..e16d988bf1 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -256,5 +256,7 @@ mod errors { CannotAffordLockCost, /// exceeded the rate limit for associating an EVM key. EvmKeyAssociateRateLimitExceeded, + /// Same auto stake destination already set + SameAutoStakeDestinationAlreadySet, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index f2d134c189..c34219d532 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -442,5 +442,19 @@ mod events { /// The minimum allowed UIDs for a subnet have been set. MinAllowedUidsSet(NetUid, u16), + + /// The auto stake destination has been set. + /// + /// - **coldkey**: The account ID of the coldkey. + /// - **netuid**: The network identifier. + /// - **hotkey**: The account ID of the hotkey. + AutoStakeDestinationSet { + /// The account ID of the coldkey. + coldkey: T::AccountId, + /// The network identifier. + netuid: NetUid, + /// The account ID of the hotkey. + hotkey: T::AccountId, + }, } } diff --git a/pallets/subtensor/src/tests/auto_stake_hotkey.rs b/pallets/subtensor/src/tests/auto_stake_hotkey.rs new file mode 100644 index 0000000000..ce3ca53166 --- /dev/null +++ b/pallets/subtensor/src/tests/auto_stake_hotkey.rs @@ -0,0 +1,112 @@ +use super::mock::*; +use crate::*; +use frame_support::{assert_noop, assert_ok}; +use sp_core::U256; +use subtensor_runtime_common::NetUid; + +#[test] +fn test_set_coldkey_auto_stake_hotkey_subnet_not_exists() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid = NetUid::from(999); // Non-existent subnet + + assert_noop!( + SubtensorModule::set_coldkey_auto_stake_hotkey( + RuntimeOrigin::signed(coldkey), + netuid, + hotkey, + ), + Error::::SubnetNotExists + ); + }); +} + +#[test] +fn test_set_coldkey_auto_stake_hotkey_hotkey_not_registered() { + new_test_ext(1).execute_with(|| { + let subnet_owner_ck = U256::from(0); + let subnet_owner_hk = U256::from(1); + + let coldkey = U256::from(10); + let hotkey = U256::from(11); // Hotkey not registered in subnet + + let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + + assert_noop!( + SubtensorModule::set_coldkey_auto_stake_hotkey( + RuntimeOrigin::signed(coldkey), + netuid, + hotkey, + ), + Error::::HotKeyNotRegisteredInSubNet + ); + }); +} + +#[test] +fn test_set_coldkey_auto_stake_hotkey_success() { + new_test_ext(1).execute_with(|| { + let subnet_owner_ck = U256::from(0); + let subnet_owner_hk = U256::from(1); + + let coldkey = U256::from(10); + let hotkey = U256::from(11); + + Owner::::insert(hotkey, coldkey); + OwnedHotkeys::::insert(coldkey, vec![hotkey]); + + let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + Uids::::insert(netuid, hotkey, 1); + + // Verify no destination is set initially + assert_eq!(AutoStakeDestination::::get(&coldkey, netuid), None); + + // Call should succeed + assert_ok!(SubtensorModule::set_coldkey_auto_stake_hotkey( + RuntimeOrigin::signed(coldkey), + netuid, + hotkey, + )); + + // Verify destination is now set + assert_eq!( + AutoStakeDestination::::get(&coldkey, netuid), + Some(hotkey) + ); + }); +} + +#[test] +fn test_set_coldkey_auto_stake_hotkey_same_hotkey_again() { + new_test_ext(1).execute_with(|| { + let subnet_owner_ck = U256::from(0); + let subnet_owner_hk = U256::from(1); + + let coldkey = U256::from(10); + let hotkey = U256::from(11); + + Owner::::insert(hotkey, coldkey); + OwnedHotkeys::::insert(coldkey, vec![hotkey]); + + let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + Uids::::insert(netuid, hotkey, 1); + + // First call should succeed + assert_ok!(SubtensorModule::set_coldkey_auto_stake_hotkey( + RuntimeOrigin::signed(coldkey), + netuid, + hotkey, + )); + + // Second call with same hotkey should fail + assert_noop!( + SubtensorModule::set_coldkey_auto_stake_hotkey( + RuntimeOrigin::signed(coldkey), + netuid, + hotkey, + ), + Error::::SameAutoStakeDestinationAlreadySet + ); + }); +} diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index b53308ae9f..215d5849d8 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -2876,3 +2876,4 @@ fn test_incentive_goes_to_hotkey_when_no_autostake_destination() { ); }); } + diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index b9f4ff5366..a0105a6ffe 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -1,3 +1,4 @@ +mod auto_stake_hotkey; mod batch_tx; mod children; mod coinbase; From 36bbc4c8b2c7766401ef00919cb9355305a1d704 Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 29 Sep 2025 20:09:21 +0800 Subject: [PATCH 14/19] cargo clippy --- pallets/subtensor/src/tests/auto_stake_hotkey.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/tests/auto_stake_hotkey.rs b/pallets/subtensor/src/tests/auto_stake_hotkey.rs index ce3ca53166..96290dbfaf 100644 --- a/pallets/subtensor/src/tests/auto_stake_hotkey.rs +++ b/pallets/subtensor/src/tests/auto_stake_hotkey.rs @@ -60,7 +60,7 @@ fn test_set_coldkey_auto_stake_hotkey_success() { Uids::::insert(netuid, hotkey, 1); // Verify no destination is set initially - assert_eq!(AutoStakeDestination::::get(&coldkey, netuid), None); + assert_eq!(AutoStakeDestination::::get(coldkey, netuid), None); // Call should succeed assert_ok!(SubtensorModule::set_coldkey_auto_stake_hotkey( @@ -71,7 +71,7 @@ fn test_set_coldkey_auto_stake_hotkey_success() { // Verify destination is now set assert_eq!( - AutoStakeDestination::::get(&coldkey, netuid), + AutoStakeDestination::::get(coldkey, netuid), Some(hotkey) ); }); From e52c4b46353e1d2fbb2a0ac8ec63e814fe5d3ac7 Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 29 Sep 2025 20:10:03 +0800 Subject: [PATCH 15/19] cargo fmt --- pallets/subtensor/src/tests/coinbase.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 215d5849d8..b53308ae9f 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -2876,4 +2876,3 @@ fn test_incentive_goes_to_hotkey_when_no_autostake_destination() { ); }); } - From a1c0eba2eae4fcafcd8e9a6a54b244f78c1d38f2 Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 29 Sep 2025 23:13:23 +0800 Subject: [PATCH 16/19] fix bench test --- pallets/subtensor/src/benchmarks.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index ed8a448765..5b73ac3f41 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1561,9 +1561,20 @@ mod pallet_benchmarks { fn set_coldkey_auto_stake_hotkey() { let coldkey: T::AccountId = whitelisted_caller(); let netuid = NetUid::from(1); - let hot: T::AccountId = account("A", 0, 1); + let hotkey: T::AccountId = account("A", 0, 1); + SubtokenEnabled::::insert(netuid, true); + Subtensor::::init_new_network(netuid, 1); + let amount = 900_000_000_000; + + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount); + + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(coldkey.clone()).into(), + netuid, + hotkey.clone() + )); #[extrinsic_call] - _(RawOrigin::Signed(coldkey.clone()), netuid, hot.clone()); + _(RawOrigin::Signed(coldkey.clone()), netuid, hotkey.clone()); } } From 5fc25cd3419374e78ed1942f107dd3cfbf14dd73 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 30 Sep 2025 01:45:23 +0000 Subject: [PATCH 17/19] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 381cde2605..2c58bb9fae 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1086,7 +1086,7 @@ mod dispatches { /// Weight is calculated based on the number of database reads and writes. #[pallet::call_index(71)] #[pallet::weight((Weight::from_parts(161_700_000, 0) - .saturating_add(T::DbWeight::get().reads(15_u64)) + .saturating_add(T::DbWeight::get().reads(16_u64)) .saturating_add(T::DbWeight::get().writes(9)), DispatchClass::Operational, Pays::No))] pub fn swap_coldkey( origin: OriginFor, @@ -2292,8 +2292,8 @@ mod dispatches { /// * `hotkey` (T::AccountId): /// - The hotkey account to designate as the autostake destination. #[pallet::call_index(114)] - #[pallet::weight((Weight::from_parts(5_170_000, 0) - .saturating_add(T::DbWeight::get().reads(0_u64)) + #[pallet::weight((Weight::from_parts(29_930_000, 0) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] pub fn set_coldkey_auto_stake_hotkey( origin: T::RuntimeOrigin, From 03826e68daf435fb65b4229029cd76ff339b7154 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 30 Sep 2025 09:51:33 +0800 Subject: [PATCH 18/19] rename error --- pallets/subtensor/src/macros/dispatches.rs | 2 +- pallets/subtensor/src/macros/errors.rs | 4 ++-- pallets/subtensor/src/tests/auto_stake_hotkey.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 2c58bb9fae..8883fefa3a 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2311,7 +2311,7 @@ mod dispatches { if let Some(current_hotkey) = current_hotkey { ensure!( current_hotkey != hotkey, - Error::::SameAutoStakeDestinationAlreadySet + Error::::SameAutoStakeHotkeyAlreadySet ); } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index e16d988bf1..922335518e 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -256,7 +256,7 @@ mod errors { CannotAffordLockCost, /// exceeded the rate limit for associating an EVM key. EvmKeyAssociateRateLimitExceeded, - /// Same auto stake destination already set - SameAutoStakeDestinationAlreadySet, + /// Same auto stake hotkey already set + SameAutoStakeHotkeyAlreadySet, } } diff --git a/pallets/subtensor/src/tests/auto_stake_hotkey.rs b/pallets/subtensor/src/tests/auto_stake_hotkey.rs index 96290dbfaf..242d469c22 100644 --- a/pallets/subtensor/src/tests/auto_stake_hotkey.rs +++ b/pallets/subtensor/src/tests/auto_stake_hotkey.rs @@ -106,7 +106,7 @@ fn test_set_coldkey_auto_stake_hotkey_same_hotkey_again() { netuid, hotkey, ), - Error::::SameAutoStakeDestinationAlreadySet + Error::::SameAutoStakeHotkeyAlreadySet ); }); } From 1dc5e0e5893522673b5722ffb9bb97a4df83d249 Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 1 Oct 2025 20:08:57 +0800 Subject: [PATCH 19/19] bump version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 37a10b6fa5..931653bfc3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -223,7 +223,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: 323, + spec_version: 324, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,