From 993f033e23072a5a6f1aaccefdebfcb0698dc303 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 26 Nov 2025 23:10:34 -0300 Subject: [PATCH 01/62] clean up swap coldkey --- pallets/subtensor/src/macros/dispatches.rs | 2 +- pallets/subtensor/src/macros/genesis.rs | 1 - pallets/subtensor/src/swap/swap_coldkey.rs | 278 ++++++-------------- pallets/subtensor/src/tests/claim_root.rs | 2 - pallets/subtensor/src/tests/swap_coldkey.rs | 83 +----- 5 files changed, 91 insertions(+), 275 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index edf561810e..da115591cc 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1090,7 +1090,7 @@ mod dispatches { old_coldkey: T::AccountId, new_coldkey: T::AccountId, swap_cost: TaoCurrency, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { // Ensure it's called with root privileges (scheduler has root privileges) ensure_root(origin)?; log::debug!("swap_coldkey: {:?} -> {:?}", old_coldkey, new_coldkey); diff --git a/pallets/subtensor/src/macros/genesis.rs b/pallets/subtensor/src/macros/genesis.rs index 7bf8ba2a53..6924e04511 100644 --- a/pallets/subtensor/src/macros/genesis.rs +++ b/pallets/subtensor/src/macros/genesis.rs @@ -101,7 +101,6 @@ mod genesis { netuid, U64F64::saturating_from_num(1_000_000_000), ); - // TotalColdkeyAlpha::::insert(hotkey.clone(), netuid, 1_000_000_000); SubnetAlphaOut::::insert(netuid, AlphaCurrency::from(1_000_000_000)); let mut staking_hotkeys = StakingHotkeys::::get(hotkey.clone()); if !staking_hotkeys.contains(&hotkey) { diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index c81138b58c..d061c50173 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -1,228 +1,136 @@ use super::*; -use frame_support::weights::Weight; -use sp_core::Get; use substrate_fixed::types::U64F64; impl Pallet { - /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey. - /// - /// # Arguments - /// - /// * `origin` - The origin of the call, which must be signed by the old coldkey. - /// * `new_coldkey` - The account ID of the new coldkey. - /// - /// # Returns - /// - /// Returns a `DispatchResultWithPostInfo` indicating success or failure, along with the weight consumed. - /// - /// # Errors - /// - /// This function will return an error if: - /// - The caller is not a valid signed origin. - /// - The old coldkey (caller) is in arbitration. - /// - The new coldkey is already associated with other hotkeys or is a hotkey itself. - /// - There's not enough balance to pay for the swap. - /// - /// # Events - /// - /// Emits a `ColdkeySwapped` event when successful. - /// - /// # Weight - /// - /// Weight is tracked and updated throughout the function execution. + /// Logic for the coldkey swap operation. Checks for collisions, balances, and cooldowns + /// before executing the swap. pub fn do_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, swap_cost: TaoCurrency, - ) -> DispatchResultWithPostInfo { - // 2. Initialize the weight for this operation - let mut weight: Weight = T::DbWeight::get().reads(2); - // 3. Ensure the new coldkey is not associated with any hotkeys + ) -> DispatchResult { ensure!( StakingHotkeys::::get(new_coldkey).is_empty(), Error::::ColdKeyAlreadyAssociated ); - weight = weight.saturating_add(T::DbWeight::get().reads(1)); - - // 4. Ensure the new coldkey is not a hotkey ensure!( !Self::hotkey_account_exists(new_coldkey), Error::::NewColdKeyIsHotkey ); - weight = weight.saturating_add(T::DbWeight::get().reads(1)); - - // 5. Swap the identity if the old coldkey has one - if let Some(identity) = IdentitiesV2::::take(old_coldkey) { - IdentitiesV2::::insert(new_coldkey, identity); - } - - // 6. Ensure sufficient balance for the swap cost ensure!( Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost.into()), Error::::NotEnoughBalanceToPaySwapColdKey ); - // 7. Remove and recycle the swap cost from the old coldkey's account - let actual_burn_amount = - Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost.into())?; - Self::recycle_tao(actual_burn_amount); + // Swap the identity if the old coldkey has one + if let Some(identity) = IdentitiesV2::::take(old_coldkey) { + IdentitiesV2::::insert(new_coldkey, identity); + } - // 8. Update the weight for the balance operations - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + // Remove and recycle the swap cost from the old coldkey's account + let burn_amount = Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost.into())?; + Self::recycle_tao(burn_amount); - // 9. Perform the actual coldkey swap - let _ = Self::perform_swap_coldkey(old_coldkey, new_coldkey, &mut weight); + Self::perform_swap_coldkey(old_coldkey, new_coldkey)?; - // 10. Update the last transaction block for the new coldkey Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - // 11. Remove the coldkey swap scheduled record ColdkeySwapScheduled::::remove(old_coldkey); - // 12. Emit the ColdkeySwapped event Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), swap_cost, }); - - // 12. Return the result with the updated weight - Ok(Some(weight).into()) + Ok(()) } - /// Performs the actual coldkey swap operation, transferring all associated data and balances from the old coldkey to the new coldkey. - /// - /// # Arguments - /// - /// * `old_coldkey` - The account ID of the old coldkey. - /// * `new_coldkey` - The account ID of the new coldkey. - /// * `weight` - A mutable reference to the current transaction weight. - /// - /// # Returns - /// - /// Returns a `DispatchResult` indicating success or failure of the operation. - /// - /// # Steps - /// - /// 1. Swap TotalHotkeyColdkeyStakesThisInterval: - /// - For each hotkey owned by the old coldkey, transfer its stake and block data to the new coldkey. - /// - /// 2. Swap subnet ownership: - /// - For each subnet, if the old coldkey is the owner, transfer ownership to the new coldkey. - /// - /// 3. Swap Stakes: - /// - For each hotkey staking for the old coldkey, transfer its stake to the new coldkey. + /// Transfer all assets, stakes, subnet ownerships, and hotkey associations from `old_coldkey` to `new_coldkey`. /// - /// 4. Swap total coldkey stake: - /// - Transfer the total stake from the old coldkey to the new coldkey. - /// - /// 5. Swap StakingHotkeys: - /// - Transfer the list of staking hotkeys from the old coldkey to the new coldkey. - /// - /// 6. Swap hotkey owners: - /// - For each hotkey owned by the old coldkey, transfer ownership to the new coldkey. - /// - Update the list of owned hotkeys for both old and new coldkeys. - /// - /// 7. Transfer remaining balance: - /// - Transfer any remaining balance from the old coldkey to the new coldkey. - /// - /// Throughout the process, the function updates the transaction weight to reflect the operations performed. - /// - /// # Notes - /// - /// This function is a critical part of the coldkey swap process and should be called only after all necessary checks and validations have been performed. + /// # Warning + /// This function performs NO validation checks. It assumes the caller has done all the checks before calling it. pub fn perform_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, - weight: &mut Weight, ) -> DispatchResult { - // 1. Swap TotalHotkeyColdkeyStakesThisInterval - // TotalHotkeyColdkeyStakesThisInterval: MAP ( hotkey, coldkey ) --> ( stake, block ) | Stake of the hotkey for the coldkey. - // for hotkey in OwnedHotkeys::::get(old_coldkey).iter() { - // let (stake, block) = - // TotalHotkeyColdkeyStakesThisInterval::::get(&hotkey, old_coldkey); - // TotalHotkeyColdkeyStakesThisInterval::::remove(&hotkey, old_coldkey); - // TotalHotkeyColdkeyStakesThisInterval::::insert(&hotkey, new_coldkey, (stake, block)); - // weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // } (DEPRECATED) - - // 2. Swap subnet owner. - // SubnetOwner: MAP ( netuid ) --> (coldkey) | Owner of the subnet. for netuid in Self::get_all_subnet_netuids() { - let subnet_owner = SubnetOwner::::get(netuid); - if subnet_owner == *old_coldkey { - SubnetOwner::::insert(netuid, new_coldkey.clone()); - } - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Self::transfer_subnet_ownership(netuid, old_coldkey, new_coldkey); + Self::transfer_coldkey_stake(netuid, old_coldkey, new_coldkey); + } - 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.clone(), - ); - AutoStakeDestinationColdkeys::::mutate(old_auto_stake_hotkey, netuid, |v| { - // Remove old/new coldkeys (avoid duplicates), then add the new one. - v.retain(|c| *c != *old_coldkey && *c != *new_coldkey); - v.push(new_coldkey.clone()); - }); - } + Self::transfer_staking_hotkeys(old_coldkey, new_coldkey); + Self::transfer_hotkeys_ownership(old_coldkey, new_coldkey); + + // Transfer any remaining balance from old_coldkey to new_coldkey + let remaining_balance = Self::get_coldkey_balance(old_coldkey); + if remaining_balance > 0 { + Self::kill_coldkey_account(old_coldkey, remaining_balance)?; + Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); + } + + Ok(()) + } + + /// Transfer the ownership of the subnet to the new coldkey if it is owned by the old coldkey. + fn transfer_subnet_ownership( + netuid: NetUid, + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + ) { + let subnet_owner = SubnetOwner::::get(netuid); + if subnet_owner == *old_coldkey { + SubnetOwner::::insert(netuid, new_coldkey.clone()); + } + + 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.clone()); + AutoStakeDestinationColdkeys::::mutate(old_auto_stake_hotkey, netuid, |v| { + // Remove old/new coldkeys (avoid duplicates), then add the new one. + v.retain(|c| *c != *old_coldkey && *c != *new_coldkey); + v.push(new_coldkey.clone()); + }); } + } - // 3. Swap Stake. - // StakingHotkeys: MAP ( coldkey ) --> Vec( hotkey ) + /// Transfer the stake of all staking hotkeys linked to the old coldkey to the new coldkey. + fn transfer_coldkey_stake( + netuid: NetUid, + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + ) { for hotkey in StakingHotkeys::::get(old_coldkey) { - // 3.1 Swap Alpha - for netuid in Self::get_all_subnet_netuids() { - // Get the stake on the old (hot,coldkey) account. - let old_alpha: U64F64 = Alpha::::get((&hotkey, old_coldkey, netuid)); - // Get the stake on the new (hot,coldkey) account. - let new_alpha: U64F64 = Alpha::::get((&hotkey, new_coldkey, netuid)); - // Add the stake to new account. - Alpha::::insert( - (&hotkey, new_coldkey, netuid), - new_alpha.saturating_add(old_alpha), + // Get the stake on the old (hot,coldkey) account. + let old_alpha: U64F64 = Alpha::::get((&hotkey, old_coldkey, netuid)); + // Get the stake on the new (hot,coldkey) account. + let new_alpha: U64F64 = Alpha::::get((&hotkey, new_coldkey, netuid)); + // Add the stake to new account. + Alpha::::insert( + (&hotkey, new_coldkey, netuid), + new_alpha.saturating_add(old_alpha), + ); + // Remove the value from the old account. + Alpha::::remove((&hotkey, old_coldkey, netuid)); + + if new_alpha.saturating_add(old_alpha) > U64F64::from(0u64) { + Self::transfer_root_claimed_for_new_keys( + netuid, + &hotkey, + &hotkey, + old_coldkey, + new_coldkey, ); - // Remove the value from the old account. - Alpha::::remove((&hotkey, old_coldkey, netuid)); - if new_alpha.saturating_add(old_alpha) > U64F64::from(0u64) { - Self::transfer_root_claimed_for_new_keys( - netuid, - &hotkey, - &hotkey, - old_coldkey, - new_coldkey, - ); - - if netuid == NetUid::ROOT { - // Register new coldkey with root stake - Self::maybe_add_coldkey_index(new_coldkey); - } + if netuid == NetUid::ROOT { + // Register new coldkey with root stake + Self::maybe_add_coldkey_index(new_coldkey); } } - // Add the weight for the read and write. - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } + } - // 4. Swap TotalColdkeyAlpha (DEPRECATED) - // for netuid in Self::get_all_subnet_netuids() { - // let old_alpha_stake: u64 = TotalColdkeyAlpha::::get(old_coldkey, netuid); - // let new_alpha_stake: u64 = TotalColdkeyAlpha::::get(new_coldkey, netuid); - // TotalColdkeyAlpha::::insert( - // new_coldkey, - // netuid, - // new_alpha_stake.saturating_add(old_alpha_stake), - // ); - // TotalColdkeyAlpha::::remove(old_coldkey, netuid); - // } - // weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - - // 5. Swap StakingHotkeys. - // StakingHotkeys: MAP ( coldkey ) --> Vec | Hotkeys staking for the coldkey. + /// Transfer staking hotkeys from the old coldkey to the new coldkey. + fn transfer_staking_hotkeys(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId) { let old_staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); let mut new_staking_hotkeys: Vec = StakingHotkeys::::get(new_coldkey); for hotkey in old_staking_hotkeys { @@ -231,13 +139,13 @@ impl Pallet { new_staking_hotkeys.push(hotkey); } } + StakingHotkeys::::remove(old_coldkey); StakingHotkeys::::insert(new_coldkey, new_staking_hotkeys); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } - // 6. Swap hotkey owners. - // Owner: MAP ( hotkey ) --> coldkey | Owner of the hotkey. - // OwnedHotkeys: MAP ( coldkey ) --> Vec | Hotkeys owned by the coldkey. + /// Transfer the ownership of the hotkeys owned by the old coldkey to the new coldkey. + fn transfer_hotkeys_ownership(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId) { let old_owned_hotkeys: Vec = OwnedHotkeys::::get(old_coldkey); let mut new_owned_hotkeys: Vec = OwnedHotkeys::::get(new_coldkey); for owned_hotkey in old_owned_hotkeys.iter() { @@ -252,19 +160,5 @@ impl Pallet { } OwnedHotkeys::::remove(old_coldkey); OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - - // 7. Transfer remaining balance. - // Balance: MAP ( coldkey ) --> u64 | Balance of the coldkey. - // Transfer any remaining balance from old_coldkey to new_coldkey - let remaining_balance = Self::get_coldkey_balance(old_coldkey); - if remaining_balance > 0 { - Self::kill_coldkey_account(old_coldkey, remaining_balance)?; - Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); - } - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - - // Return ok. - Ok(()) } } diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index b910fa1e83..4a98d23b63 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1184,12 +1184,10 @@ fn test_claim_root_with_swap_coldkey() { ); // Swap coldkey - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &coldkey, &new_coldkey, - &mut weight )); // Check swapped keys claimed values diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 9d3bdbfc62..090d743f3a 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -13,7 +13,6 @@ use frame_support::error::BadOrigin; use frame_support::traits::OnInitialize; use frame_support::traits::schedule::DispatchTime; use frame_support::traits::schedule::v3::Named as ScheduleNamed; -use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok}; use frame_system::{Config, RawOrigin}; use sp_core::{Get, H256, U256}; @@ -28,36 +27,6 @@ use super::mock::*; use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; use crate::{Call, ColdkeySwapScheduleDuration, Error}; -// // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_total_hotkey_coldkey_stakes_this_interval --exact --nocapture -// #[test] -// fn test_swap_total_hotkey_coldkey_stakes_this_interval() { -// new_test_ext(1).execute_with(|| { -// let old_coldkey = U256::from(1); -// let new_coldkey = U256::from(2); -// let hotkey = U256::from(3); -// let stake = 100; -// let block = 42; - -// OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); -// TotalHotkeyColdkeyStakesThisInterval::::insert(hotkey, old_coldkey, (stake, block)); - -// let mut weight = Weight::zero(); -// assert_ok!(SubtensorModule::perform_swap_coldkey( -// &old_coldkey, -// &new_coldkey, -// &mut weight -// )); - -// assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( -// hotkey, -// old_coldkey -// )); -// assert_eq!( -// TotalHotkeyColdkeyStakesThisInterval::::get(hotkey, new_coldkey), -// (stake, block) -// ); -// }); -// } // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_subnet_owner --exact --nocapture #[test] @@ -70,11 +39,9 @@ fn test_swap_subnet_owner() { add_network(netuid, 1, 0); SubnetOwner::::insert(netuid, old_coldkey); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!(SubnetOwner::::get(netuid), new_coldkey); @@ -115,11 +82,9 @@ fn test_swap_total_coldkey_stake() { )); let total_stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!( @@ -143,11 +108,9 @@ fn test_swap_staking_hotkeys() { StakingHotkeys::::insert(old_coldkey, vec![hotkey]); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert!(StakingHotkeys::::get(old_coldkey).is_empty()); @@ -166,11 +129,9 @@ fn test_swap_hotkey_owners() { Owner::::insert(hotkey, old_coldkey); OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!(Owner::::get(hotkey), new_coldkey); @@ -188,11 +149,9 @@ fn test_transfer_remaining_balance() { SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, balance); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); @@ -207,11 +166,9 @@ fn test_swap_with_no_stake() { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!( @@ -236,11 +193,9 @@ fn test_swap_with_multiple_hotkeys() { OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); @@ -265,11 +220,9 @@ fn test_swap_with_multiple_subnets() { SubnetOwner::::insert(netuid1, old_coldkey); SubnetOwner::::insert(netuid2, old_coldkey); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); @@ -284,11 +237,9 @@ fn test_swap_with_zero_balance() { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!(Balances::free_balance(old_coldkey), 0); @@ -324,16 +275,13 @@ fn test_swap_idempotency() { // Get stake before swap let stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!( @@ -405,16 +353,13 @@ fn test_swap_with_max_values() { netuid2, ); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey2, &new_coldkey2, - &mut weight )); assert_eq!( @@ -469,11 +414,9 @@ fn test_swap_with_non_existent_new_coldkey() { netuid, ); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!( @@ -505,11 +448,9 @@ fn test_swap_with_max_hotkeys() { OwnedHotkeys::::insert(old_coldkey, hotkeys.clone()); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); @@ -552,11 +493,9 @@ fn test_swap_effect_on_delegated_stake() { let coldkey_stake_before = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); let delegator_stake_before = SubtensorModule::get_total_stake_for_coldkey(&delegator); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_abs_diff_eq!( @@ -630,11 +569,9 @@ fn test_swap_concurrent_modifications() { netuid, ); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); assert_eq!( @@ -663,11 +600,9 @@ fn test_swap_with_invalid_subnet_ownership() { // Simulate an invalid state where the subnet owner doesn't match the old_coldkey SubnetOwner::::insert(netuid, U256::from(3)); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); // The swap should not affect the mismatched subnet ownership @@ -865,7 +800,6 @@ fn test_swap_stake_for_coldkey() { let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; let stake_amount3 = DefaultMinStake::::get().to_u64() * 30; - let mut weight = Weight::zero(); // Setup initial state // Add a network @@ -936,7 +870,7 @@ fn test_swap_stake_for_coldkey() { let initial_total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey); // Verify stake is additive, not replaced assert_abs_diff_eq!( @@ -1021,7 +955,6 @@ fn test_swap_staking_hotkeys_for_coldkey() { let hotkey2 = U256::from(5); let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; - let mut weight = Weight::zero(); // Setup initial state // Add a network @@ -1064,7 +997,7 @@ fn test_swap_staking_hotkeys_for_coldkey() { ); // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey); // Verify StakingHotkeys transfer assert_eq!( @@ -1086,7 +1019,6 @@ fn test_swap_delegated_stake_for_coldkey() { let hotkey2 = U256::from(5); let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; - let mut weight = Weight::zero(); let netuid = NetUid::from(1); // Setup initial state @@ -1150,7 +1082,7 @@ fn test_swap_delegated_stake_for_coldkey() { let total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey); // Verify stake transfer assert_eq!( @@ -1228,7 +1160,6 @@ fn test_swap_subnet_owner_for_coldkey() { let new_coldkey = U256::from(2); let netuid1 = NetUid::from(1); let netuid2 = NetUid::from(2); - let mut weight = Weight::zero(); // Initialize SubnetOwner for old_coldkey add_network(netuid1, 13, 0); @@ -1240,7 +1171,7 @@ fn test_swap_subnet_owner_for_coldkey() { TotalNetworks::::put(3); // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey); // Verify the swap assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); @@ -1523,11 +1454,9 @@ fn test_coldkey_swap_total() { SubtensorModule::get_total_stake_for_coldkey(&coldkey), ck_stake ); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &coldkey, &new_coldkey, - &mut weight )); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), @@ -1664,11 +1593,9 @@ fn test_coldkey_delegations() { )); // Perform the swap - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &coldkey, &new_coldkey, - &mut weight )); // Verify stake was moved for the delegate @@ -2594,11 +2521,9 @@ fn test_swap_auto_stake_destination_coldkeys() { AutoStakeDestinationColdkeys::::insert(hotkey, netuid, coldkeys.clone()); AutoStakeDestination::::insert(old_coldkey, netuid, hotkey); - let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, - &mut weight )); let new_coldkeys = AutoStakeDestinationColdkeys::::get(hotkey, netuid); From 14fe3f6caeca57244644df07c46d589d9d659a30 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 12:45:30 +0000 Subject: [PATCH 02/62] deprecate old calls --- pallets/subtensor/src/macros/dispatches.rs | 129 +++------------------ 1 file changed, 16 insertions(+), 113 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index da115591cc..45b0de46ee 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -6,11 +6,10 @@ use frame_support::pallet_macros::pallet_section; #[pallet_section] mod dispatches { use crate::subnets::leasing::SubnetLeasingWeightInfo; - use frame_support::traits::schedule::DispatchTime; use frame_support::traits::schedule::v3::Anon as ScheduleAnon; use frame_system::pallet_prelude::BlockNumberFor; use sp_core::ecdsa::Signature; - use sp_runtime::{Percent, traits::Saturating}; + use sp_runtime::Percent; use crate::MAX_CRV3_COMMIT_SIZE_BYTES; use crate::MAX_NUM_ROOT_CLAIMS; @@ -1068,34 +1067,17 @@ mod dispatches { /// The extrinsic for user to change the coldkey associated with their account. /// - /// # Arguments - /// - /// * `origin` - The origin of the call, must be signed by the old coldkey. - /// * `old_coldkey` - The current coldkey associated with the account. - /// * `new_coldkey` - The new coldkey to be associated with the account. - /// - /// # Returns - /// - /// Returns a `DispatchResultWithPostInfo` indicating success or failure of the operation. - /// - /// # Weight - /// - /// Weight is calculated based on the number of database reads and writes. + /// WARNING: This is deprecated in favor of `announce_coldkey_swap`/`coldkey_swap` #[pallet::call_index(71)] - #[pallet::weight((Weight::from_parts(161_700_000, 0) - .saturating_add(T::DbWeight::get().reads(16_u64)) - .saturating_add(T::DbWeight::get().writes(9)), DispatchClass::Operational, Pays::Yes))] + #[pallet::weight(Weight::zero())] + #[deprecated(note = "Deprecated, please migrate to `announce_coldkey_swap`/`coldkey_swap`")] pub fn swap_coldkey( - origin: OriginFor, - old_coldkey: T::AccountId, - new_coldkey: T::AccountId, - swap_cost: TaoCurrency, + _origin: OriginFor, + _old_coldkey: T::AccountId, + _new_coldkey: T::AccountId, + _swap_cost: TaoCurrency, ) -> DispatchResult { - // Ensure it's called with root privileges (scheduler has root privileges) - ensure_root(origin)?; - log::debug!("swap_coldkey: {:?} -> {:?}", old_coldkey, new_coldkey); - - Self::do_swap_coldkey(&old_coldkey, &new_coldkey, swap_cost) + Err(Error::::Deprecated.into()) } /// Sets the childkey take for a given hotkey. @@ -1332,94 +1314,15 @@ mod dispatches { /// Schedules a coldkey swap operation to be executed at a future block. /// - /// This function allows a user to schedule the swapping of their coldkey to a new one - /// at a specified future block. The swap is not executed immediately but is scheduled - /// to occur at the specified block number. - /// - /// # Arguments - /// - /// * `origin` - The origin of the call, which should be signed by the current coldkey owner. - /// * `new_coldkey` - The account ID of the new coldkey that will replace the current one. - /// * `when` - The block number at which the coldkey swap should be executed. - /// - /// # Returns - /// - /// Returns a `DispatchResultWithPostInfo` indicating whether the scheduling was successful. - /// - /// # Errors - /// - /// This function may return an error if: - /// * The origin is not signed. - /// * The scheduling fails due to conflicts or system constraints. - /// - /// # Notes - /// - /// - The actual swap is not performed by this function. It merely schedules the swap operation. - /// - The weight of this call is set to a fixed value and may need adjustment based on benchmarking. - /// - /// # TODO - /// - /// - Implement proper weight calculation based on the complexity of the operation. - /// - Consider adding checks to prevent scheduling too far into the future. - /// TODO: Benchmark this call + /// WARNING: This function is deprecated, please migrate to `announce_coldkey_swap`/`coldkey_swap` #[pallet::call_index(73)] - #[pallet::weight((Weight::from_parts(37_830_000, 0) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight(Weight::zero())] + #[deprecated(note = "Deprecated, please migrate to `announce_coldkey_swap`/`coldkey_swap`")] pub fn schedule_swap_coldkey( - origin: OriginFor, - new_coldkey: T::AccountId, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - let current_block = >::block_number(); - - // If the coldkey has a scheduled swap, check if we can reschedule it - if ColdkeySwapScheduled::::contains_key(&who) { - let (scheduled_block, _scheduled_coldkey) = ColdkeySwapScheduled::::get(&who); - let reschedule_duration = ColdkeySwapRescheduleDuration::::get(); - let redo_when = scheduled_block.saturating_add(reschedule_duration); - ensure!(redo_when <= current_block, Error::::SwapAlreadyScheduled); - } - - // Calculate the swap cost and ensure sufficient balance - let swap_cost = Self::get_key_swap_cost(); - ensure!( - Self::can_remove_balance_from_coldkey_account(&who, swap_cost.into()), - Error::::NotEnoughBalanceToPaySwapColdKey - ); - - let current_block: BlockNumberFor = >::block_number(); - let duration: BlockNumberFor = ColdkeySwapScheduleDuration::::get(); - let when: BlockNumberFor = current_block.saturating_add(duration); - - let call = Call::::swap_coldkey { - old_coldkey: who.clone(), - new_coldkey: new_coldkey.clone(), - swap_cost, - }; - - let bound_call = ::Preimages::bound(LocalCallOf::::from(call.clone())) - .map_err(|_| Error::::FailedToSchedule)?; - - T::Scheduler::schedule( - DispatchTime::At(when), - None, - 63, - frame_system::RawOrigin::Root.into(), - bound_call, - ) - .map_err(|_| Error::::FailedToSchedule)?; - - ColdkeySwapScheduled::::insert(&who, (when, new_coldkey.clone())); - // Emit the SwapScheduled event - Self::deposit_event(Event::ColdkeySwapScheduled { - old_coldkey: who.clone(), - new_coldkey: new_coldkey.clone(), - execution_block: when, - swap_cost, - }); - - Ok(().into()) + _origin: OriginFor, + _new_coldkey: T::AccountId, + ) -> DispatchResult { + Err(Error::::Deprecated.into()) } /// ---- Set prometheus information for the neuron. From 7c485f0fd7748a4194555384a3e6ee58040fc3c3 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 12:46:07 +0000 Subject: [PATCH 03/62] add new storage for announcements --- pallets/subtensor/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index dd7645e8ff..d19ff35ebe 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1369,6 +1369,12 @@ pub mod pallet { DefaultColdkeySwapScheduled, >; + /// A map of the coldkey swap announcements from a coldkey + /// to the block number the announcement was made and the new coldkey. + #[pallet::storage] + pub type ColdkeySwapAnnouncements = + StorageMap<_, Twox64Concat, T::AccountId, (BlockNumberFor, T::AccountId), OptionQuery>; + /// --- DMAP ( hot, netuid ) --> alpha | Returns the total amount of alpha a hotkey owns. #[pallet::storage] pub type TotalHotkeyAlpha = StorageDoubleMap< From 54eded034e418f51fb4eda0c0a64e72492903c32 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 12:46:55 +0000 Subject: [PATCH 04/62] remove perform_swap_coldkey --- pallets/subtensor/src/swap/swap_coldkey.rs | 61 ++++++++-------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index d061c50173..e11ef2b53f 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -2,72 +2,57 @@ use super::*; use substrate_fixed::types::U64F64; impl Pallet { - /// Logic for the coldkey swap operation. Checks for collisions, balances, and cooldowns - /// before executing the swap. + /// Transfer all assets, stakes, subnet ownerships, and hotkey associations from `old_coldkey` to + /// to `new_coldkey`. pub fn do_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, - swap_cost: TaoCurrency, ) -> DispatchResult { ensure!( - StakingHotkeys::::get(new_coldkey).is_empty(), + StakingHotkeys::::get(&new_coldkey).is_empty(), Error::::ColdKeyAlreadyAssociated ); ensure!( - !Self::hotkey_account_exists(new_coldkey), + !Self::hotkey_account_exists(&new_coldkey), Error::::NewColdKeyIsHotkey ); + + // Remove and recycle the swap cost from the old coldkey's account + let swap_cost = Self::get_key_swap_cost(); ensure!( Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost.into()), Error::::NotEnoughBalanceToPaySwapColdKey ); + let burn_amount = Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost.into())?; + Self::recycle_tao(burn_amount); // Swap the identity if the old coldkey has one if let Some(identity) = IdentitiesV2::::take(old_coldkey) { - IdentitiesV2::::insert(new_coldkey, identity); + IdentitiesV2::::insert(new_coldkey.clone(), identity); } - // Remove and recycle the swap cost from the old coldkey's account - let burn_amount = Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost.into())?; - Self::recycle_tao(burn_amount); - - Self::perform_swap_coldkey(old_coldkey, new_coldkey)?; - - Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); - - ColdkeySwapScheduled::::remove(old_coldkey); - - Self::deposit_event(Event::ColdkeySwapped { - old_coldkey: old_coldkey.clone(), - new_coldkey: new_coldkey.clone(), - swap_cost, - }); - Ok(()) - } - - /// Transfer all assets, stakes, subnet ownerships, and hotkey associations from `old_coldkey` to `new_coldkey`. - /// - /// # Warning - /// This function performs NO validation checks. It assumes the caller has done all the checks before calling it. - pub fn perform_swap_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - ) -> DispatchResult { for netuid in Self::get_all_subnet_netuids() { - Self::transfer_subnet_ownership(netuid, old_coldkey, new_coldkey); - Self::transfer_coldkey_stake(netuid, old_coldkey, new_coldkey); + Self::transfer_subnet_ownership(netuid, old_coldkey, &new_coldkey); + Self::transfer_coldkey_stake(netuid, old_coldkey, &new_coldkey); } - - Self::transfer_staking_hotkeys(old_coldkey, new_coldkey); - Self::transfer_hotkeys_ownership(old_coldkey, new_coldkey); + Self::transfer_staking_hotkeys(old_coldkey, &new_coldkey); + Self::transfer_hotkeys_ownership(old_coldkey, &new_coldkey); // Transfer any remaining balance from old_coldkey to new_coldkey let remaining_balance = Self::get_coldkey_balance(old_coldkey); if remaining_balance > 0 { Self::kill_coldkey_account(old_coldkey, remaining_balance)?; - Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); + Self::add_balance_to_coldkey_account(&new_coldkey, remaining_balance); } + Self::set_last_tx_block(&new_coldkey, Self::get_current_block_as_u64()); + ColdkeySwapAnnouncements::::remove(old_coldkey); + + Self::deposit_event(Event::ColdkeySwapped { + old_coldkey: old_coldkey.clone(), + new_coldkey: new_coldkey.clone(), + swap_cost, + }); Ok(()) } From d16bba55a829675c018e6b1a57f21c01e85a10b6 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 21:30:03 +0100 Subject: [PATCH 05/62] added new extrinsics --- pallets/subtensor/src/macros/dispatches.rs | 57 ++++++++++++++++++++++ pallets/subtensor/src/swap/swap_coldkey.rs | 1 - 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 45b0de46ee..3ec118a563 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2334,5 +2334,62 @@ mod dispatches { Ok(()) } + + /// Announces a coldkey swap. This is required before the coldkey swap can be performed after the delay period. + #[pallet::call_index(125)] + #[pallet::weight(Weight::zero())] + pub fn announce_coldkey_swap( + origin: OriginFor, + new_coldkey: T::AccountId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let now = >::block_number(); + + ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey.clone())); + + Self::deposit_event(Event::ColdkeySwapAnnounced { + who: who.clone(), + new_coldkey: new_coldkey.clone(), + block_number: now, + }); + Ok(()) + } + + /// Removes a coldkey swap announcement. + #[pallet::call_index(126)] + #[pallet::weight(Weight::zero())] + pub fn remove_coldkey_swap_announcement(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!( + ColdkeySwapAnnouncements::::contains_key(who.clone()), + Error::::ColdkeySwapAnnouncementNotFound + ); + + ColdkeySwapAnnouncements::::remove(who.clone()); + + Self::deposit_event(Event::ColdkeySwapAnnouncementRemoved { who: who.clone() }); + Ok(()) + } + + /// Performs a coldkey swap iff an announcement has been made. + #[pallet::call_index(127)] + #[pallet::weight(Weight::zero())] + pub fn coldkey_swap_announced(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + + let (when, new_coldkey) = ColdkeySwapAnnouncements::::take(who.clone()) + .ok_or(Error::::ColdkeySwapAnnouncementNotFound)?; + + let now = >::block_number(); + let delay = when + ColdkeySwapScheduleDuration::::get(); + ensure!(now >= delay, Error::::ColdkeySwapTooEarly); + + Self::do_swap_coldkey(&who, &new_coldkey)?; + + ColdkeySwapAnnouncements::::remove(who.clone()); + + Ok(()) + } } } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index e11ef2b53f..885e211300 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -46,7 +46,6 @@ impl Pallet { } Self::set_last_tx_block(&new_coldkey, Self::get_current_block_as_u64()); - ColdkeySwapAnnouncements::::remove(old_coldkey); Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), From dfdf22dd481c252cce93b6333887ca77bb6c4b15 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 21:30:13 +0100 Subject: [PATCH 06/62] update errors --- pallets/subtensor/src/macros/errors.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 5a15330075..e3221b573a 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -150,10 +150,10 @@ mod errors { TooManyChildren, /// Default transaction rate limit exceeded. TxRateLimitExceeded, - /// Swap already scheduled. - SwapAlreadyScheduled, - /// failed to swap coldkey - FailedToSchedule, + /// Coldkey swap announcement not found + ColdkeySwapAnnouncementNotFound, + /// Coldkey swap too early. + ColdkeySwapTooEarly, /// New coldkey is hotkey NewColdKeyIsHotkey, /// Childkey take is invalid. @@ -266,5 +266,7 @@ mod errors { InvalidRootClaimThreshold, /// Exceeded subnet limit number or zero. InvalidSubnetNumber, + /// Deprecated call. + Deprecated, } } From ed2bab6fb37f17b0402d934f443971258b2cb93d Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 21:32:32 +0100 Subject: [PATCH 07/62] updated events --- pallets/subtensor/src/macros/events.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index d015205d4d..3efc74e413 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -170,6 +170,20 @@ mod events { MaxDelegateTakeSet(u16), /// minimum delegate take is set by sudo/admin transaction MinDelegateTakeSet(u16), + /// A coldkey swap announcement has been made. + ColdkeySwapAnnounced { + /// The account ID of the coldkey that made the announcement. + who: T::AccountId, + /// The account ID of the new coldkey. + new_coldkey: T::AccountId, + /// The block number the announcement was made. + block_number: BlockNumberFor, + }, + /// A coldkey swap announcement has been removed. + ColdkeySwapAnnouncementRemoved { + /// The account ID of the coldkey that made the announcement. + who: T::AccountId, + }, /// A coldkey has been swapped ColdkeySwapped { /// the account ID of old coldkey @@ -190,17 +204,6 @@ mod events { ::AccountId, >>::Balance, }, - /// A coldkey swap has been scheduled - ColdkeySwapScheduled { - /// The account ID of the old coldkey - old_coldkey: T::AccountId, - /// The account ID of the new coldkey - new_coldkey: T::AccountId, - /// The arbitration block for the coldkey swap - execution_block: BlockNumberFor, - /// The swap cost - swap_cost: TaoCurrency, - }, /// The arbitration period has been extended ArbitrationPeriodExtended { /// The account ID of the coldkey From 7b4bdab75e5371df6ca86e8e0b4b49d80d7ed569 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 21:33:00 +0100 Subject: [PATCH 08/62] fix claim root test --- pallets/subtensor/src/tests/claim_root.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 4a98d23b63..0158579553 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1184,11 +1184,9 @@ fn test_claim_root_with_swap_coldkey() { ); // Swap coldkey - - assert_ok!(SubtensorModule::perform_swap_coldkey( - &coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost.to_u64()); + assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); // Check swapped keys claimed values From d92a19b3f1046bb343b3dad134e8f6b745b88c71 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 21:47:45 +0100 Subject: [PATCH 09/62] added new tests for swap coldkey announce/remove --- pallets/subtensor/src/tests/swap_coldkey.rs | 131 +++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 090d743f3a..9f0787ec2f 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -28,7 +28,100 @@ use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; use crate::{Call, ColdkeySwapScheduleDuration, Error}; -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_subnet_owner --exact --nocapture +#[test] +fn test_announce_coldkey_swap_works() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(1); + + assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey, + )); + + let now = System::block_number(); + assert_eq!( + ColdkeySwapAnnouncements::::iter().collect::>(), + vec![(who.clone(), (now, new_coldkey))] + ); + assert_eq!( + last_event(), + RuntimeEvent::SubtensorModule(Event::ColdkeySwapAnnounced { + who, + new_coldkey, + block_number: now, + }) + ); + }); +} + +#[test] +fn test_announce_coldkey_swap_bad_origin_fails() { + new_test_ext(1).execute_with(|| { + let new_coldkey = U256::from(1); + + assert_noop!( + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::none(), new_coldkey), + BadOrigin + ); + + assert_noop!( + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::root(), new_coldkey), + BadOrigin + ); + }); +} + +#[test] +fn test_remove_coldkey_swap_announcement_works() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + let now = System::block_number(); + ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey)); + + assert_ok!(SubtensorModule::remove_coldkey_swap_announcement( + RuntimeOrigin::signed(who.clone()), + )); + + assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); + assert_eq!( + last_event(), + RuntimeEvent::SubtensorModule(Event::ColdkeySwapAnnouncementRemoved { who }) + ); + }); +} + +#[test] +fn test_remove_coldkey_swap_announcement_fails_if_no_announcement_exists() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + + assert_noop!( + SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::signed(who.clone())), + Error::::ColdkeySwapAnnouncementNotFound + ); + }); +} + +#[test] +fn test_remove_coldkey_swap_announcement_bad_origin_fails() { + new_test_ext(1).execute_with(|| { + assert_noop!( + SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::none()), + BadOrigin + ); + + assert_noop!( + SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::root()), + BadOrigin + ); + }); +} + #[test] fn test_swap_subnet_owner() { new_test_ext(1).execute_with(|| { @@ -2539,3 +2632,39 @@ fn test_swap_auto_stake_destination_coldkeys() { ); }); } + +#[test] +#[allow(deprecated)] +fn test_swap_coldkey_deprecated() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + assert_noop!( + SubtensorModule::swap_coldkey( + <::RuntimeOrigin>::root(), + old_coldkey, + new_coldkey, + TaoCurrency::MAX + ), + Error::::Deprecated + ); + }); +} + +#[test] +#[allow(deprecated)] +fn test_schedule_swap_coldkey_deprecated() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + assert_noop!( + SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::root(), + new_coldkey, + ), + Error::::Deprecated + ); + }); +} From a6f7fc6b44883eb7d53abaa760ac27a611080a74 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 21:52:41 +0100 Subject: [PATCH 10/62] No need to remove because we take --- pallets/subtensor/src/macros/dispatches.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 3ec118a563..8a6d8fd610 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2387,8 +2387,6 @@ mod dispatches { Self::do_swap_coldkey(&who, &new_coldkey)?; - ColdkeySwapAnnouncements::::remove(who.clone()); - Ok(()) } } From 40f4d9ad246417148e5baa6bda6cf8bd42e2285c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 30 Nov 2025 22:33:02 +0100 Subject: [PATCH 11/62] renamed to swap_coldkey_announced --- pallets/subtensor/src/macros/dispatches.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 8a6d8fd610..f9da6ef853 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2375,7 +2375,7 @@ mod dispatches { /// Performs a coldkey swap iff an announcement has been made. #[pallet::call_index(127)] #[pallet::weight(Weight::zero())] - pub fn coldkey_swap_announced(origin: OriginFor) -> DispatchResult { + pub fn swap_coldkey_announced(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let (when, new_coldkey) = ColdkeySwapAnnouncements::::take(who.clone()) From c5b033043047400ee85ff0aeed193decfff2c8ab Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 2 Dec 2025 17:07:02 +0100 Subject: [PATCH 12/62] update SubtensorTransactionExtension --- .../subtensor/src/transaction_extension.rs | 134 +++++++----------- 1 file changed, 52 insertions(+), 82 deletions(-) diff --git a/pallets/subtensor/src/transaction_extension.rs b/pallets/subtensor/src/transaction_extension.rs index cf1d410ea9..7a9e226eb7 100644 --- a/pallets/subtensor/src/transaction_extension.rs +++ b/pallets/subtensor/src/transaction_extension.rs @@ -1,24 +1,24 @@ -use crate::{ - BalancesCall, Call, ColdkeySwapScheduled, Config, CustomTransactionError, Error, Pallet, - TransactionType, -}; +use crate::{BalancesCall, Call, Config, CustomTransactionError, Error, Pallet, TransactionType}; use codec::{Decode, DecodeWithMemTracking, Encode}; use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; -use frame_support::pallet_prelude::Weight; use frame_support::traits::IsSubType; use scale_info::TypeInfo; use sp_runtime::traits::{ AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Implication, TransactionExtension, ValidateResult, }; -use sp_runtime::transaction_validity::{ - TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, +use sp_runtime::{ + impl_tx_ext_default, + transaction_validity::{TransactionSource, TransactionValidity, ValidTransaction}, }; use sp_std::marker::PhantomData; use sp_std::vec::Vec; use subtensor_macros::freeze_struct; use subtensor_runtime_common::{NetUid, NetUidStorageIndex}; +type CallOf = ::RuntimeCall; +type OriginOf = ::RuntimeOrigin; + #[freeze_struct("2e02eb32e5cb25d3")] #[derive(Default, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] pub struct SubtensorTransactionExtension(pub PhantomData); @@ -31,9 +31,7 @@ impl sp_std::fmt::Debug for SubtensorTransac impl SubtensorTransactionExtension where - ::RuntimeCall: - Dispatchable, - ::RuntimeCall: IsSubType>, + CallOf: Dispatchable + IsSubType>, { pub fn new() -> Self { Self(Default::default()) @@ -52,30 +50,29 @@ where pub fn result_to_validity(result: Result<(), Error>, priority: u64) -> TransactionValidity { if let Err(err) = result { Err(match err { - Error::::AmountTooLow => CustomTransactionError::StakeAmountTooLow.into(), - Error::::SubnetNotExists => CustomTransactionError::SubnetNotExists.into(), - Error::::NotEnoughBalanceToStake => CustomTransactionError::BalanceTooLow.into(), + Error::::AmountTooLow => CustomTransactionError::StakeAmountTooLow, + Error::::SubnetNotExists => CustomTransactionError::SubnetNotExists, + Error::::NotEnoughBalanceToStake => CustomTransactionError::BalanceTooLow, Error::::HotKeyAccountNotExists => { - CustomTransactionError::HotkeyAccountDoesntExist.into() + CustomTransactionError::HotkeyAccountDoesntExist } Error::::NotEnoughStakeToWithdraw => { - CustomTransactionError::NotEnoughStakeToWithdraw.into() - } - Error::::InsufficientLiquidity => { - CustomTransactionError::InsufficientLiquidity.into() + CustomTransactionError::NotEnoughStakeToWithdraw } - Error::::SlippageTooHigh => CustomTransactionError::SlippageTooHigh.into(), - Error::::TransferDisallowed => CustomTransactionError::TransferDisallowed.into(), + Error::::InsufficientLiquidity => CustomTransactionError::InsufficientLiquidity, + Error::::SlippageTooHigh => CustomTransactionError::SlippageTooHigh, + Error::::TransferDisallowed => CustomTransactionError::TransferDisallowed, Error::::HotKeyNotRegisteredInNetwork => { - CustomTransactionError::HotKeyNotRegisteredInNetwork.into() + CustomTransactionError::HotKeyNotRegisteredInNetwork } - Error::::InvalidIpAddress => CustomTransactionError::InvalidIpAddress.into(), + Error::::InvalidIpAddress => CustomTransactionError::InvalidIpAddress, Error::::ServingRateLimitExceeded => { - CustomTransactionError::ServingRateLimitExceeded.into() + CustomTransactionError::ServingRateLimitExceeded } - Error::::InvalidPort => CustomTransactionError::InvalidPort.into(), - _ => CustomTransactionError::BadRequest.into(), - }) + Error::::InvalidPort => CustomTransactionError::InvalidPort, + _ => CustomTransactionError::BadRequest, + } + .into()) } else { Ok(ValidTransaction { priority, @@ -89,52 +86,41 @@ impl TransactionExtension<::RuntimeCall> for SubtensorTransactionExtension where - ::RuntimeCall: - Dispatchable, - ::RuntimeOrigin: AsSystemOriginSigner + Clone, - ::RuntimeCall: IsSubType>, - ::RuntimeCall: IsSubType>, + CallOf: Dispatchable + + IsSubType> + + IsSubType>, + OriginOf: AsSystemOriginSigner + Clone, { const IDENTIFIER: &'static str = "SubtensorTransactionExtension"; type Implicit = (); - type Val = Option; + type Val = (); type Pre = (); - fn weight(&self, _call: &::RuntimeCall) -> Weight { - // TODO: benchmark transaction extension - Weight::zero() - } - fn validate( &self, - origin: ::RuntimeOrigin, - call: &::RuntimeCall, - _info: &DispatchInfoOf<::RuntimeCall>, + origin: OriginOf, + call: &CallOf, + _info: &DispatchInfoOf>, _len: usize, _self_implicit: Self::Implicit, _inherited_implication: &impl Implication, _source: TransactionSource, - ) -> ValidateResult::RuntimeCall> { + ) -> ValidateResult> { // Ensure the transaction is signed, else we just skip the extension. let Some(who) = origin.as_system_origin_signer() else { - return Ok((Default::default(), None, origin)); + return Ok((Default::default(), (), origin)); }; - // Verify ColdkeySwapScheduled map for coldkey - match call.is_sub_type() { - // Whitelist - Some(Call::schedule_swap_coldkey { .. }) => {} - _ => { - if ColdkeySwapScheduled::::contains_key(who) { - return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); - } - } + // Ensure the origin coldkey is not announced for a swap. + if !matches!(call.is_sub_type(), Some(Call::announce_coldkey_swap { .. })) { + return Err(CustomTransactionError::ColdkeySwapAnnounced.into()); } + match call.is_sub_type() { Some(Call::commit_weights { netuid, .. }) => { if Self::check_weights_min_stake(who, *netuid) { - Ok((Default::default(), Some(who.clone()), origin)) + Ok((Default::default(), (), origin)) } else { Err(CustomTransactionError::StakeAmountTooLow.into()) } @@ -158,7 +144,7 @@ where match Pallet::::find_commit_block_via_hash(provided_hash) { Some(commit_block) => { if Pallet::::is_reveal_block_range(*netuid, commit_block) { - Ok((Default::default(), Some(who.clone()), origin)) + Ok((Default::default(), (), origin)) } else { Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) } @@ -203,7 +189,7 @@ where if provided_hashes.len() == batch_reveal_block.len() { if Pallet::::is_batch_reveal_block_range(*netuid, batch_reveal_block) { - Ok((Default::default(), Some(who.clone()), origin)) + Ok((Default::default(), (), origin)) } else { Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) } @@ -219,7 +205,7 @@ where } Some(Call::set_weights { netuid, .. }) => { if Self::check_weights_min_stake(who, *netuid) { - Ok((Default::default(), Some(who.clone()), origin)) + Ok((Default::default(), (), origin)) } else { Err(CustomTransactionError::StakeAmountTooLow.into()) } @@ -233,7 +219,7 @@ where if *reveal_round < pallet_drand::LastStoredRound::::get() { return Err(CustomTransactionError::InvalidRevealRound.into()); } - Ok((Default::default(), Some(who.clone()), origin)) + Ok((Default::default(), (), origin)) } else { Err(CustomTransactionError::StakeAmountTooLow.into()) } @@ -249,7 +235,7 @@ where return Err(CustomTransactionError::RateLimitExceeded.into()); } - Ok((Default::default(), Some(who.clone()), origin)) + Ok((Default::default(), (), origin)) } Some(Call::serve_axon { netuid, @@ -276,41 +262,25 @@ where ), 0u64, ) - .map(|validity| (validity, Some(who.clone()), origin.clone())) + .map(|validity| (validity, (), origin.clone())) } Some(Call::register_network { .. }) => { if !TransactionType::RegisterNetwork.passes_rate_limit::(who) { return Err(CustomTransactionError::RateLimitExceeded.into()); } - Ok((Default::default(), Some(who.clone()), origin)) + Ok((Default::default(), (), origin)) } Some(Call::associate_evm_key { netuid, .. }) => { - match Pallet::::get_uid_for_net_and_hotkey(*netuid, who) { - Ok(uid) => { - match Pallet::::ensure_evm_key_associate_rate_limit(*netuid, uid) { - Ok(_) => Ok((Default::default(), Some(who.clone()), origin)), - Err(_) => { - Err(CustomTransactionError::EvmKeyAssociateRateLimitExceeded.into()) - } - } - } - Err(_) => Err(CustomTransactionError::UidNotFound.into()), - } + let uid = Pallet::::get_uid_for_net_and_hotkey(*netuid, who) + .map_err(|_| CustomTransactionError::UidNotFound)?; + Pallet::::ensure_evm_key_associate_rate_limit(*netuid, uid) + .map_err(|_| CustomTransactionError::EvmKeyAssociateRateLimitExceeded)?; + Ok((Default::default(), (), origin)) } - _ => Ok((Default::default(), Some(who.clone()), origin)), + _ => Ok((Default::default(), (), origin)), } } - // NOTE: Add later when we put in a pre and post dispatch step. - fn prepare( - self, - _val: Self::Val, - _origin: &::RuntimeOrigin, - _call: &::RuntimeCall, - _info: &DispatchInfoOf<::RuntimeCall>, - _len: usize, - ) -> Result { - Ok(()) - } + impl_tx_ext_default!(::RuntimeCall; weight prepare); } From e96625a06b9c321ccc167806de4da47883cb64cf Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 2 Dec 2025 20:05:08 +0100 Subject: [PATCH 13/62] fix announcement --- pallets/subtensor/src/macros/dispatches.rs | 32 ++++----- pallets/subtensor/src/macros/errors.rs | 2 + pallets/subtensor/src/tests/swap_coldkey.rs | 79 +++++++++++++-------- 3 files changed, 64 insertions(+), 49 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index f9da6ef853..e64f7eae53 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2345,6 +2345,15 @@ mod dispatches { let who = ensure_signed(origin)?; let now = >::block_number(); + if let Some(existing) = ColdkeySwapAnnouncements::::get(who.clone()) { + let delay = ColdkeySwapScheduleDuration::::get(); + let when = existing.0; + ensure!( + now > when + delay, + Error::::ColdkeySwapReannouncedTooEarly + ); + } + ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey.clone())); Self::deposit_event(Event::ColdkeySwapAnnounced { @@ -2355,25 +2364,8 @@ mod dispatches { Ok(()) } - /// Removes a coldkey swap announcement. - #[pallet::call_index(126)] - #[pallet::weight(Weight::zero())] - pub fn remove_coldkey_swap_announcement(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!( - ColdkeySwapAnnouncements::::contains_key(who.clone()), - Error::::ColdkeySwapAnnouncementNotFound - ); - - ColdkeySwapAnnouncements::::remove(who.clone()); - - Self::deposit_event(Event::ColdkeySwapAnnouncementRemoved { who: who.clone() }); - Ok(()) - } - /// Performs a coldkey swap iff an announcement has been made. - #[pallet::call_index(127)] + #[pallet::call_index(126)] #[pallet::weight(Weight::zero())] pub fn swap_coldkey_announced(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -2382,8 +2374,8 @@ mod dispatches { .ok_or(Error::::ColdkeySwapAnnouncementNotFound)?; let now = >::block_number(); - let delay = when + ColdkeySwapScheduleDuration::::get(); - ensure!(now >= delay, Error::::ColdkeySwapTooEarly); + let delay = ColdkeySwapScheduleDuration::::get(); + ensure!(now > when + delay, Error::::ColdkeySwapTooEarly); Self::do_swap_coldkey(&who, &new_coldkey)?; diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index e3221b573a..e83a7fc747 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -154,6 +154,8 @@ mod errors { ColdkeySwapAnnouncementNotFound, /// Coldkey swap too early. ColdkeySwapTooEarly, + /// Coldkey swap reannounced too early. + ColdkeySwapReannouncedTooEarly, /// New coldkey is hotkey NewColdKeyIsHotkey, /// Childkey take is invalid. diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 9f0787ec2f..989317c3bd 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -29,10 +29,10 @@ use crate::*; use crate::{Call, ColdkeySwapScheduleDuration, Error}; #[test] -fn test_announce_coldkey_swap_works() { +fn test_announce_coldkey_swap_with_no_announcement_works() { new_test_ext(1).execute_with(|| { let who = U256::from(1); - let new_coldkey = U256::from(1); + let new_coldkey = U256::from(2); assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); @@ -58,65 +58,86 @@ fn test_announce_coldkey_swap_works() { } #[test] -fn test_announce_coldkey_swap_bad_origin_fails() { +fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { new_test_ext(1).execute_with(|| { - let new_coldkey = U256::from(1); + let who = U256::from(1); + let new_coldkey = U256::from(2); + let new_coldkey_2 = U256::from(3); - assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::none(), new_coldkey), - BadOrigin + assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey, + )); + + let now = System::block_number(); + assert_eq!( + ColdkeySwapAnnouncements::::iter().collect::>(), + vec![(who.clone(), (now, new_coldkey))] ); - assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::root(), new_coldkey), - BadOrigin + let delay = ColdkeySwapScheduleDuration::::get() + 1; + System::run_to_block::(now + delay); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey_2, + )); + + let now = System::block_number(); + assert_eq!( + ColdkeySwapAnnouncements::::iter().collect::>(), + vec![(who.clone(), (now, new_coldkey_2))] ); }); } #[test] -fn test_remove_coldkey_swap_announcement_works() { +fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); - let now = System::block_number(); - ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey)); + let new_coldkey_2 = U256::from(3); + + assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); - assert_ok!(SubtensorModule::remove_coldkey_swap_announcement( + assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), + new_coldkey, )); - assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); + let now = System::block_number(); assert_eq!( - last_event(), - RuntimeEvent::SubtensorModule(Event::ColdkeySwapAnnouncementRemoved { who }) + ColdkeySwapAnnouncements::::iter().collect::>(), + vec![(who.clone(), (now, new_coldkey))] ); - }); -} -#[test] -fn test_remove_coldkey_swap_announcement_fails_if_no_announcement_exists() { - new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let new_coldkey = U256::from(2); + let unmet_delay = ColdkeySwapScheduleDuration::::get(); + System::run_to_block::(now + unmet_delay); assert_noop!( - SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::signed(who.clone())), - Error::::ColdkeySwapAnnouncementNotFound + SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey_2, + ), + Error::::ColdkeySwapReannouncedTooEarly ); }); } #[test] -fn test_remove_coldkey_swap_announcement_bad_origin_fails() { +fn test_announce_coldkey_swap_with_bad_origin_fails() { new_test_ext(1).execute_with(|| { + let new_coldkey = U256::from(1); + assert_noop!( - SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::none()), + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::none(), new_coldkey), BadOrigin ); assert_noop!( - SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::root()), + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::root(), new_coldkey), BadOrigin ); }); From 949e93643757e1276b4efd84b9edd96f5a67afea Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 2 Dec 2025 20:07:25 +0100 Subject: [PATCH 14/62] fix most tests --- pallets/subtensor/src/tests/swap_coldkey.rs | 804 +++++--------------- 1 file changed, 207 insertions(+), 597 deletions(-) diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 989317c3bd..060d87c36d 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -8,7 +8,7 @@ use approx::assert_abs_diff_eq; use codec::Encode; -use frame_support::dispatch::DispatchInfo; +use frame_support::dispatch::{DispatchInfo, GetDispatchInfo}; use frame_support::error::BadOrigin; use frame_support::traits::OnInitialize; use frame_support::traits::schedule::DispatchTime; @@ -16,7 +16,7 @@ use frame_support::traits::schedule::v3::Named as ScheduleNamed; use frame_support::{assert_err, assert_noop, assert_ok}; use frame_system::{Config, RawOrigin}; use sp_core::{Get, H256, U256}; -use sp_runtime::traits::{DispatchInfoOf, TransactionExtension}; +use sp_runtime::traits::{DispatchInfoOf, DispatchTransaction, TransactionExtension}; use sp_runtime::{DispatchError, traits::TxBaseImplication}; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, SubnetInfo, TaoCurrency}; @@ -153,16 +153,15 @@ fn test_swap_subnet_owner() { add_network(netuid, 1, 0); SubnetOwner::::insert(netuid, old_coldkey); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_eq!(SubnetOwner::::get(netuid), new_coldkey); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_total_coldkey_stake --exact --show-output #[test] fn test_swap_total_coldkey_stake() { new_test_ext(1).execute_with(|| { @@ -196,10 +195,10 @@ fn test_swap_total_coldkey_stake() { )); let total_stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), @@ -212,7 +211,6 @@ fn test_swap_total_coldkey_stake() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_staking_hotkeys --exact --nocapture #[test] fn test_swap_staking_hotkeys() { new_test_ext(1).execute_with(|| { @@ -222,17 +220,16 @@ fn test_swap_staking_hotkeys() { StakingHotkeys::::insert(old_coldkey, vec![hotkey]); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert!(StakingHotkeys::::get(old_coldkey).is_empty()); assert_eq!(StakingHotkeys::::get(new_coldkey), vec![hotkey]); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_hotkey_owners --exact --nocapture #[test] fn test_swap_hotkey_owners() { new_test_ext(1).execute_with(|| { @@ -243,17 +240,17 @@ fn test_swap_hotkey_owners() { Owner::::insert(hotkey, old_coldkey); OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_eq!(Owner::::get(hotkey), new_coldkey); assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); assert_eq!(OwnedHotkeys::::get(new_coldkey), vec![hotkey]); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_transfer_remaining_balance --exact --nocapture + #[test] fn test_transfer_remaining_balance() { new_test_ext(1).execute_with(|| { @@ -263,27 +260,26 @@ fn test_transfer_remaining_balance() { SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, balance); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_with_no_stake --exact --show-output #[test] fn test_swap_with_no_stake() { new_test_ext(1).execute_with(|| { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), @@ -296,7 +292,6 @@ fn test_swap_with_no_stake() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_multiple_hotkeys --exact --nocapture #[test] fn test_swap_with_multiple_hotkeys() { new_test_ext(1).execute_with(|| { @@ -307,10 +302,10 @@ fn test_swap_with_multiple_hotkeys() { OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); assert_eq!( @@ -320,7 +315,6 @@ fn test_swap_with_multiple_hotkeys() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_multiple_subnets --exact --nocapture #[test] fn test_swap_with_multiple_subnets() { new_test_ext(1).execute_with(|| { @@ -334,82 +328,51 @@ fn test_swap_with_multiple_subnets() { SubnetOwner::::insert(netuid1, old_coldkey); SubnetOwner::::insert(netuid2, old_coldkey); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_zero_balance --exact --nocapture +// TODO #[test] fn test_swap_with_zero_balance() { new_test_ext(1).execute_with(|| { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); - - assert_eq!(Balances::free_balance(old_coldkey), 0); - assert_eq!(Balances::free_balance(new_coldkey), 0); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_idempotency --exact --show-output -#[test] -fn test_swap_idempotency() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let netuid = NetUid::from(1u16); - let stake = DefaultMinStake::::get().to_u64() * 10; - let reserve = stake * 10; - - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - // Add a network - add_network(netuid, 1, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake); // Give old coldkey some balance - // Stake to a hotkey - register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey, - netuid, - stake.into() - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - // Get stake before swap - let stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); + println!( + "old_coldkey balance: {}", + Balances::free_balance(old_coldkey) + ); + println!( + "new_coldkey balance: {}", + Balances::free_balance(new_coldkey) + ); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), - TaoCurrency::ZERO + println!( + "old_coldkey balance: {}", + Balances::free_balance(old_coldkey) ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - stake_before_swap + println!( + "new_coldkey balance: {}", + Balances::free_balance(new_coldkey) ); + + assert_eq!(Balances::free_balance(old_coldkey), 0); + assert_eq!(Balances::free_balance(new_coldkey), 0); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_with_max_values --exact --show-output #[test] fn test_swap_with_max_values() { new_test_ext(1).execute_with(|| { @@ -467,13 +430,14 @@ fn test_swap_with_max_values() { netuid2, ); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); - assert_ok!(SubtensorModule::perform_swap_coldkey( + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey2, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); + assert_ok!(SubtensorModule::do_swap_coldkey( &old_coldkey2, - &new_coldkey2, + &new_coldkey2 )); assert_eq!( @@ -497,7 +461,6 @@ fn test_swap_with_max_values() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_with_non_existent_new_coldkey --exact --show-output #[test] fn test_swap_with_non_existent_new_coldkey() { new_test_ext(1).execute_with(|| { @@ -528,10 +491,10 @@ fn test_swap_with_non_existent_new_coldkey() { netuid, ); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), @@ -551,7 +514,6 @@ fn test_swap_with_non_existent_new_coldkey() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_max_hotkeys --exact --nocapture #[test] fn test_swap_with_max_hotkeys() { new_test_ext(1).execute_with(|| { @@ -562,17 +524,16 @@ fn test_swap_with_max_hotkeys() { OwnedHotkeys::::insert(old_coldkey, hotkeys.clone()); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); assert_eq!(OwnedHotkeys::::get(new_coldkey), hotkeys); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_effect_on_delegated_stake --exact --nocapture #[test] fn test_swap_effect_on_delegated_stake() { new_test_ext(1).execute_with(|| { @@ -607,10 +568,10 @@ fn test_swap_effect_on_delegated_stake() { let coldkey_stake_before = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); let delegator_stake_before = SubtensorModule::get_total_stake_for_coldkey(&delegator); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), @@ -630,7 +591,7 @@ fn test_swap_effect_on_delegated_stake() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_concurrent_modifications --exact --show-output +// TODO #[test] fn test_swap_concurrent_modifications() { new_test_ext(1).execute_with(|| { @@ -683,10 +644,10 @@ fn test_swap_concurrent_modifications() { netuid, ); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -701,7 +662,6 @@ fn test_swap_concurrent_modifications() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_invalid_subnet_ownership --exact --nocapture #[test] fn test_swap_with_invalid_subnet_ownership() { new_test_ext(1).execute_with(|| { @@ -714,17 +674,16 @@ fn test_swap_with_invalid_subnet_ownership() { // Simulate an invalid state where the subnet owner doesn't match the old_coldkey SubnetOwner::::insert(netuid, U256::from(3)); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // The swap should not affect the mismatched subnet ownership assert_eq!(SubnetOwner::::get(netuid), U256::from(3)); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_do_swap_coldkey_success --exact --show-output #[test] fn test_do_swap_coldkey_success() { new_test_ext(1).execute_with(|| { @@ -819,12 +778,7 @@ fn test_do_swap_coldkey_success() { let total_ck_stake = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey( - // <::RuntimeOrigin>::signed(old_coldkey), - &old_coldkey, - &new_coldkey, - swap_cost - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Log state after swap log::info!( @@ -903,7 +857,6 @@ fn test_do_swap_coldkey_success() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_stake_for_coldkey --exact --show-output #[test] fn test_swap_stake_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -983,8 +936,11 @@ fn test_swap_stake_for_coldkey() { let initial_total_hotkey1_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey1); let initial_total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Verify stake is additive, not replaced assert_abs_diff_eq!( @@ -1058,7 +1014,6 @@ fn test_swap_stake_for_coldkey() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_staking_hotkeys_for_coldkey --exact --show-output #[test] fn test_swap_staking_hotkeys_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -1110,8 +1065,11 @@ fn test_swap_staking_hotkeys_for_coldkey() { netuid, ); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Verify StakingHotkeys transfer assert_eq!( @@ -1122,7 +1080,6 @@ fn test_swap_staking_hotkeys_for_coldkey() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_delegated_stake_for_coldkey --exact --show-output #[test] fn test_swap_delegated_stake_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -1195,8 +1152,11 @@ fn test_swap_delegated_stake_for_coldkey() { let total_hotkey1_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey1); let total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Verify stake transfer assert_eq!( @@ -1266,7 +1226,6 @@ fn test_swap_delegated_stake_for_coldkey() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_swap_subnet_owner_for_coldkey --exact --nocapture #[test] fn test_swap_subnet_owner_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -1284,8 +1243,11 @@ fn test_swap_subnet_owner_for_coldkey() { // Set up TotalNetworks TotalNetworks::::put(3); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Verify the swap assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); @@ -1293,7 +1255,6 @@ fn test_swap_subnet_owner_for_coldkey() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_do_swap_coldkey_with_subnet_ownership --exact --nocapture #[test] fn test_do_swap_coldkey_with_subnet_ownership() { new_test_ext(1).execute_with(|| { @@ -1317,18 +1278,17 @@ fn test_do_swap_coldkey_with_subnet_ownership() { // Populate OwnedHotkeys map OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - swap_cost.into() - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Verify subnet ownership transfer assert_eq!(SubnetOwner::::get(netuid), new_coldkey); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_coldkey_has_associated_hotkeys --exact --nocapture + #[test] fn test_coldkey_has_associated_hotkeys() { new_test_ext(1).execute_with(|| { @@ -1343,7 +1303,6 @@ fn test_coldkey_has_associated_hotkeys() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_coldkey_swap_total --exact --show-output #[test] fn test_coldkey_swap_total() { new_test_ext(1).execute_with(|| { @@ -1562,16 +1521,16 @@ fn test_coldkey_swap_total() { vec![hotkey3, delegate3] ); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost.to_u64()); + // Perform the swap let new_coldkey = U256::from(1100); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&coldkey), ck_stake ); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &coldkey, - &new_coldkey, - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), ck_stake @@ -1660,7 +1619,6 @@ fn test_coldkey_swap_total() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_coldkey_delegations --exact --show-output #[test] fn test_coldkey_delegations() { new_test_ext(1).execute_with(|| { @@ -1706,11 +1664,11 @@ fn test_coldkey_delegations() { stake.into() )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost.to_u64()); + // Perform the swap - assert_ok!(SubtensorModule::perform_swap_coldkey( - &coldkey, - &new_coldkey, - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); // Verify stake was moved for the delegate let approx_total_stake = TaoCurrency::from(stake * 2 - fee * 2); @@ -1746,74 +1704,6 @@ fn test_coldkey_delegations() { }); } -#[test] -fn test_schedule_swap_coldkey_success() { - new_test_ext(1).execute_with(|| { - // Initialize test accounts - let old_coldkey: U256 = U256::from(1); - let new_coldkey: U256 = U256::from(2); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - - // Add balance to the old coldkey account - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64() + 1_000); - - // Schedule the coldkey swap - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey - )); - - // Get the current block number - let current_block: u64 = System::block_number(); - - // Calculate the expected execution block (5 days from now) - let expected_execution_block: u64 = current_block + 5 * 24 * 60 * 60 / 12; - - // Check for the SwapScheduled event - System::assert_last_event( - Event::ColdkeySwapScheduled { - old_coldkey, - new_coldkey, - execution_block: expected_execution_block, - swap_cost, - } - .into(), - ); - - // TODO: Add additional checks to ensure the swap is correctly scheduled in the system - // For example, verify that the swap is present in the appropriate storage or scheduler - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_schedule_swap_coldkey_duplicate --exact --nocapture -#[test] -fn test_schedule_swap_coldkey_duplicate() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64() + 2_000); - - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey - )); - - // Attempt to schedule again - assert_noop!( - SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey - ), - Error::::SwapAlreadyScheduled - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_schedule_swap_coldkey_execution --exact --show-output --nocapture #[test] fn test_schedule_swap_coldkey_execution() { new_test_ext(1).execute_with(|| { @@ -1844,28 +1734,16 @@ fn test_schedule_swap_coldkey_execution() { ); let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); // Schedule the swap - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Get the scheduled execution block let current_block = System::block_number(); let execution_block = current_block + ColdkeySwapScheduleDuration::::get(); - System::assert_last_event( - Event::ColdkeySwapScheduled { - old_coldkey, - new_coldkey, - execution_block, - swap_cost, - } - .into(), - ); - - run_to_block(execution_block - 1); + System::run_to_block::(execution_block - 1); let stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); @@ -1909,121 +1787,6 @@ fn test_schedule_swap_coldkey_execution() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_direct_swap_coldkey_call_fails --exact --nocapture -#[test] -fn test_direct_swap_coldkey_call_fails() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - assert_noop!( - SubtensorModule::swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - old_coldkey, - new_coldkey, - TaoCurrency::ZERO - ), - BadOrigin - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_schedule_swap_coldkey_with_pending_swap --exact --nocapture -#[test] -fn test_schedule_swap_coldkey_with_pending_swap() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey1 = U256::from(2); - let new_coldkey2 = U256::from(3); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64() + 1_000); - - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey1 - )); - - // Attempt to schedule another swap before the first one executes - assert_noop!( - SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey2 - ), - Error::::SwapAlreadyScheduled - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_schedule_swap_coldkey_failure_and_reschedule --exact --nocapture -#[test] -fn test_schedule_swap_coldkey_failure_and_reschedule() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey1 = U256::from(2); - let new_coldkey2 = U256::from(3); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - - // Two swaps - SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - swap_cost.to_u64() + 1_000 * 2, - ); - - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey1 - )); - - let current_block = >::block_number(); - let duration = ColdkeySwapScheduleDuration::::get(); - let when = current_block.saturating_add(duration); - - // Setup first key to fail - // -- will fail if the new coldkey is already a hotkey (has an Owner) - Owner::::insert(new_coldkey1, U256::from(4)); - - // First swap fails - run_to_block(when - 1); - next_block(); - - // Check the failure - next_block(); // Still in the scheduled-swap map - assert!(ColdkeySwapScheduled::::contains_key(old_coldkey)); - - // Try to schedule the second swap - assert_noop!( - SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey2 - ), - Error::::SwapAlreadyScheduled - ); - - // Wait for correct duration after first swap fails - let fail_duration = ColdkeySwapRescheduleDuration::::get(); - run_to_block(when + fail_duration); - - // Schedule the second swap - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey2 - )); - - let current_block = >::block_number(); - let duration = ColdkeySwapScheduleDuration::::get(); - let when = current_block.saturating_add(duration); - run_to_block(when - 1); - next_block(); - - // Check the success - next_block(); // Now in the scheduled-swap map - assert!(!ColdkeySwapScheduled::::contains_key(old_coldkey)); - }); -} - #[test] fn test_coldkey_swap_delegate_identity_updated() { new_test_ext(1).execute_with(|| { @@ -2062,11 +1825,8 @@ fn test_coldkey_swap_delegate_identity_updated() { assert!(IdentitiesV2::::get(old_coldkey).is_some()); assert!(IdentitiesV2::::get(new_coldkey).is_none()); - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - burn_cost - )); + // Perform the swap + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert!(IdentitiesV2::::get(old_coldkey).is_none()); assert!(IdentitiesV2::::get(new_coldkey).is_some()); @@ -2103,11 +1863,7 @@ fn test_coldkey_swap_no_identity_no_changes() { assert!(IdentitiesV2::::get(old_coldkey).is_none()); // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - burn_cost - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Ensure no identities have been changed assert!(IdentitiesV2::::get(old_coldkey).is_none()); @@ -2153,11 +1909,7 @@ fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { assert!(IdentitiesV2::::get(old_coldkey).is_none()); // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - burn_cost - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Ensure no identities have been changed assert!(IdentitiesV2::::get(old_coldkey).is_none()); @@ -2165,7 +1917,6 @@ fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_cant_schedule_swap_without_enough_to_burn --exact --nocapture #[test] fn test_cant_schedule_swap_without_enough_to_burn() { new_test_ext(1).execute_with(|| { @@ -2173,18 +1924,13 @@ fn test_cant_schedule_swap_without_enough_to_burn() { let new_coldkey = U256::from(4); let hotkey = U256::from(5); - let burn_cost = SubtensorModule::get_key_swap_cost(); assert_noop!( - SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey - ), + SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey), Error::::NotEnoughBalanceToPaySwapColdKey ); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_coldkey_in_swap_schedule_prevents_funds_usage --exact --show-output --nocapture #[test] fn test_coldkey_in_swap_schedule_prevents_funds_usage() { // Testing the signed extension validate function @@ -2241,17 +1987,12 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { )); // Schedule the coldkey for a swap - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(who), - new_coldkey + assert_ok!(SubtensorModule::announce_coldkey_swap( + ::RuntimeOrigin::signed(who), + new_coldkey, )); - assert!(ColdkeySwapScheduled::::contains_key(who)); - - // Setup the extension - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - let extension = SubtensorTransactionExtension::::new(); + assert!(ColdkeySwapAnnouncements::::contains_key(who)); // Try each call @@ -2261,20 +2002,12 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { netuid, amount_staked: stake.into(), }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Add stake limit @@ -2285,20 +2018,11 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { limit_price: stake.into(), allow_partial: false, }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Swap stake @@ -2308,20 +2032,11 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { destination_netuid: netuid, alpha_amount: stake.into(), }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Swap stake limit @@ -2333,20 +2048,11 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { limit_price: stake.into(), allow_partial: false, }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Move stake @@ -2357,20 +2063,11 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { destination_netuid: netuid, alpha_amount: stake.into(), }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Transfer stake @@ -2381,20 +2078,11 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { destination_netuid: netuid, alpha_amount: stake.into(), }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Transfer all @@ -2402,20 +2090,11 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { dest: new_coldkey, keep_alive: false, }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Transfer keep alive @@ -2423,20 +2102,11 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { dest: new_coldkey, value: 100_000_000_000, }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Transfer allow death @@ -2444,38 +2114,20 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { dest: new_coldkey, value: 100_000_000_000, }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Burned register let call = RuntimeCall::SubtensorModule(SubtensorCall::burned_register { netuid, hotkey }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid); @@ -2486,20 +2138,11 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { netuid, amount_unstaked: (DefaultMinStake::::get().to_u64() * 2).into(), }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); // Remove stake limit @@ -2510,46 +2153,27 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { limit_price: 123456789.into(), // should be low enough allow_partial: true, }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced ); - // Schedule swap should succeed - let call = RuntimeCall::SubtensorModule(SubtensorCall::schedule_swap_coldkey { - new_coldkey: hotkey, - }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should be ok - assert_ok!(result); + // Announce coldkey swap should succeed + let call = + RuntimeCall::SubtensorModule(SubtensorCall::announce_coldkey_swap { new_coldkey }); + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_ok!(ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0)); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_coldkey_in_swap_schedule_prevents_critical_calls --exact --show-output --nocapture #[test] -fn test_coldkey_in_swap_schedule_prevents_critical_calls() { +fn test_announced_coldkey_swap_prevents_critical_calls() { // Testing the signed extension validate function // correctly filters transactions that are critical - // while a coldkey swap is scheduled. + // while a coldkey swap is announced. new_test_ext(0).execute_with(|| { let netuid = NetUid::from(1); @@ -2587,37 +2211,24 @@ fn test_coldkey_in_swap_schedule_prevents_critical_calls() { )); // Schedule the coldkey for a swap - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(who), - new_coldkey + assert_ok!(SubtensorModule::announce_coldkey_swap( + ::RuntimeOrigin::signed(who), + new_coldkey, )); - assert!(ColdkeySwapScheduled::::contains_key(who)); - - // Setup the extension - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - let extension = SubtensorTransactionExtension::::new(); + assert!(ColdkeySwapAnnouncements::::contains_key(who)); // Try each call // Dissolve network + let ext = SubtensorTransactionExtension::::new(); let call = RuntimeCall::SubtensorModule(SubtensorCall::dissolve_network { netuid, coldkey }); - let result = extension.validate( - RawOrigin::Signed(who).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - // Should fail - assert_eq!( - // Should get an invalid transaction error - result.unwrap_err(), - CustomTransactionError::ColdkeyInSwapSchedule.into() + let info = call.get_dispatch_info(); + + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0,), + CustomTransactionError::ColdkeySwapAnnounced ); }); } @@ -2635,10 +2246,9 @@ fn test_swap_auto_stake_destination_coldkeys() { AutoStakeDestinationColdkeys::::insert(hotkey, netuid, coldkeys.clone()); AutoStakeDestination::::insert(old_coldkey, netuid, hotkey); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - )); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); let new_coldkeys = AutoStakeDestinationColdkeys::::get(hotkey, netuid); assert!(new_coldkeys.contains(&new_coldkey)); From e8cee056110cee7447b10335c8e0f58d0ff39f5d Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 3 Dec 2025 16:58:49 +0100 Subject: [PATCH 15/62] error cases tests + clean up --- pallets/subtensor/src/macros/dispatches.rs | 6 +- pallets/subtensor/src/macros/errors.rs | 8 +- pallets/subtensor/src/tests/swap_coldkey.rs | 137 ++++++++++++++------ 3 files changed, 105 insertions(+), 46 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index e64f7eae53..be92fec5a0 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2350,7 +2350,7 @@ mod dispatches { let when = existing.0; ensure!( now > when + delay, - Error::::ColdkeySwapReannouncedTooEarly + Error::::ColdKeySwapReannouncedTooEarly ); } @@ -2371,11 +2371,11 @@ mod dispatches { let who = ensure_signed(origin)?; let (when, new_coldkey) = ColdkeySwapAnnouncements::::take(who.clone()) - .ok_or(Error::::ColdkeySwapAnnouncementNotFound)?; + .ok_or(Error::::ColdKeySwapAnnouncementNotFound)?; let now = >::block_number(); let delay = ColdkeySwapScheduleDuration::::get(); - ensure!(now > when + delay, Error::::ColdkeySwapTooEarly); + ensure!(now > when + delay, Error::::ColdKeySwapTooEarly); Self::do_swap_coldkey(&who, &new_coldkey)?; diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index e83a7fc747..49bbdfd2f8 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -138,8 +138,6 @@ mod errors { ColdKeyAlreadyAssociated, /// The coldkey balance is not enough to pay for the swap NotEnoughBalanceToPaySwapColdKey, - /// The coldkey is in arbitration - ColdkeyIsInArbitration, /// Attempting to set an invalid child for a hotkey on a network. InvalidChild, /// Duplicate child when setting children. @@ -151,11 +149,11 @@ mod errors { /// Default transaction rate limit exceeded. TxRateLimitExceeded, /// Coldkey swap announcement not found - ColdkeySwapAnnouncementNotFound, + ColdKeySwapAnnouncementNotFound, /// Coldkey swap too early. - ColdkeySwapTooEarly, + ColdKeySwapTooEarly, /// Coldkey swap reannounced too early. - ColdkeySwapReannouncedTooEarly, + ColdKeySwapReannouncedTooEarly, /// New coldkey is hotkey NewColdKeyIsHotkey, /// Childkey take is invalid. diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 060d87c36d..0d4456b86a 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -29,7 +29,7 @@ use crate::*; use crate::{Call, ColdkeySwapScheduleDuration, Error}; #[test] -fn test_announce_coldkey_swap_with_no_announcement_works() { +fn test_announce_coldkey_swap_works() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); @@ -121,7 +121,7 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() RuntimeOrigin::signed(who.clone()), new_coldkey_2, ), - Error::::ColdkeySwapReannouncedTooEarly + Error::::ColdKeySwapReannouncedTooEarly ); }); } @@ -143,6 +143,103 @@ fn test_announce_coldkey_swap_with_bad_origin_fails() { }); } +#[test] +fn test_swap_coldkey_announced_too_early_fails() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey, + )); + + assert_noop!( + SubtensorModule::swap_coldkey_announced( + ::RuntimeOrigin::signed(who) + ), + Error::::ColdKeySwapTooEarly + ); + }) +} + +#[test] +fn test_swap_coldkey_announced_with_already_associated_coldkey_fails() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey, + )); + + let now = System::block_number(); + let delay = ColdkeySwapScheduleDuration::::get() + 1; + System::run_to_block::(now + delay); + + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.to_u64()); + + SubtensorModule::create_account_if_non_existent(&new_coldkey, &hotkey); + + assert_noop!( + SubtensorModule::swap_coldkey_announced( + ::RuntimeOrigin::signed(who) + ), + Error::::ColdKeyAlreadyAssociated + ); + }) +} + +#[test] +fn test_swap_coldkey_announced_using_registered_hotkey_fails() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + hotkey.clone(), + )); + + let now = System::block_number(); + let delay = ColdkeySwapScheduleDuration::::get() + 1; + System::run_to_block::(now + delay); + + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.to_u64()); + + SubtensorModule::create_account_if_non_existent(&new_coldkey, &hotkey); + + assert_noop!( + SubtensorModule::swap_coldkey_announced( + ::RuntimeOrigin::signed(who) + ), + Error::::NewColdKeyIsHotkey + ); + }) +} + +#[test] +fn test_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + + let now = System::block_number(); + let delay = ColdkeySwapScheduleDuration::::get() + 1; + System::run_to_block::(now + delay); + + assert_noop!( + SubtensorModule::do_swap_coldkey(&who, &new_coldkey), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + }); +} + #[test] fn test_swap_subnet_owner() { new_test_ext(1).execute_with(|| { @@ -662,28 +759,6 @@ fn test_swap_concurrent_modifications() { }); } -#[test] -fn test_swap_with_invalid_subnet_ownership() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let netuid = NetUid::from(1u16); - - SubnetOwner::::insert(netuid, old_coldkey); - - // Simulate an invalid state where the subnet owner doesn't match the old_coldkey - SubnetOwner::::insert(netuid, U256::from(3)); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - // The swap should not affect the mismatched subnet ownership - assert_eq!(SubnetOwner::::get(netuid), U256::from(3)); - }); -} - #[test] fn test_do_swap_coldkey_success() { new_test_ext(1).execute_with(|| { @@ -1917,20 +1992,6 @@ fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { }); } -#[test] -fn test_cant_schedule_swap_without_enough_to_burn() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(3); - let new_coldkey = U256::from(4); - let hotkey = U256::from(5); - - assert_noop!( - SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey), - Error::::NotEnoughBalanceToPaySwapColdKey - ); - }); -} - #[test] fn test_coldkey_in_swap_schedule_prevents_funds_usage() { // Testing the signed extension validate function From dcc85bc35c48b149fba426cbc18ef728b0248a18 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 4 Dec 2025 15:16:05 +0100 Subject: [PATCH 16/62] preserve new identity + tests --- pallets/subtensor/src/swap/swap_coldkey.rs | 14 +- pallets/subtensor/src/tests/swap_coldkey.rs | 516 +++++++------------- 2 files changed, 193 insertions(+), 337 deletions(-) diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 885e211300..491daf9c98 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -26,13 +26,16 @@ impl Pallet { let burn_amount = Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost.into())?; Self::recycle_tao(burn_amount); - // Swap the identity if the old coldkey has one - if let Some(identity) = IdentitiesV2::::take(old_coldkey) { + // Swap the identity if the old coldkey has one and the new coldkey doesn't + if IdentitiesV2::::get(new_coldkey).is_none() + && let Some(identity) = IdentitiesV2::::take(old_coldkey) + { IdentitiesV2::::insert(new_coldkey.clone(), identity); } for netuid in Self::get_all_subnet_netuids() { Self::transfer_subnet_ownership(netuid, old_coldkey, &new_coldkey); + Self::transfer_auto_stake_destination(netuid, old_coldkey, &new_coldkey); Self::transfer_coldkey_stake(netuid, old_coldkey, &new_coldkey); } Self::transfer_staking_hotkeys(old_coldkey, &new_coldkey); @@ -65,7 +68,14 @@ impl Pallet { if subnet_owner == *old_coldkey { SubnetOwner::::insert(netuid, new_coldkey.clone()); } + } + /// Transfer the auto stake destination from the old coldkey to the new coldkey if it is set. + fn transfer_auto_stake_destination( + netuid: NetUid, + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + ) { 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.clone()); diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 0d4456b86a..df6480495d 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -93,6 +93,23 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { }); } +#[test] +fn test_announce_coldkey_swap_with_bad_origin_fails() { + new_test_ext(1).execute_with(|| { + let new_coldkey = U256::from(1); + + assert_noop!( + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::none(), new_coldkey), + BadOrigin + ); + + assert_noop!( + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::root(), new_coldkey), + BadOrigin + ); + }); +} + #[test] fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() { new_test_ext(1).execute_with(|| { @@ -127,22 +144,180 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() } #[test] -fn test_announce_coldkey_swap_with_bad_origin_fails() { +fn test_swap_coldkey_announced_works() { new_test_ext(1).execute_with(|| { - let new_coldkey = U256::from(1); + let who = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(1001); + let hotkey2 = U256::from(1002); + let now = System::block_number(); + + // Setup networks and subnet ownerships + let netuid1 = NetUid::from(1); + let netuid2 = NetUid::from(2); + add_network(netuid1, 1, 0); + add_network(netuid2, 1, 0); + SubnetOwner::::insert(netuid1, who); + SubnetOwner::::insert(netuid2, who); + + // Setup auto stake destinations + AutoStakeDestination::::insert(who, netuid1, hotkey1); + AutoStakeDestination::::insert(who, netuid2, hotkey2); + AutoStakeDestinationColdkeys::::insert( + hotkey1, + netuid1, + vec![who, U256::from(3), U256::from(4)], + ); + AutoStakeDestinationColdkeys::::insert( + hotkey2, + netuid2, + vec![U256::from(7), U256::from(8), who], + ); + + // Setup identity + let identity = ChainIdentityV2::default(); + IdentitiesV2::::insert(who, identity.clone()); + assert_eq!(IdentitiesV2::::get(who), Some(identity.clone())); + assert!(IdentitiesV2::::get(new_coldkey).is_none()); + + // Announce the coldkey swap + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey, + )); + assert_eq!( + ColdkeySwapAnnouncements::::get(who), + Some((now, new_coldkey)) + ); + + // Run some blocks for the announcement to be past the delay + let delay = ColdkeySwapScheduleDuration::::get() + 1; + System::run_to_block::(now + delay); + + // Add balance to the old coldkey to pay for the swap cost + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.to_u64()); + + let balance_before = SubtensorModule::get_coldkey_balance(&who); + // let total_issuance_before = SubtensorModule::get_total_issuance(); + + assert_ok!(SubtensorModule::swap_coldkey_announced( + RuntimeOrigin::signed(who), + )); + + // Ensure the announcement has been consumed + assert_eq!(ColdkeySwapAnnouncements::::get(who), None); + + // Ensure the cost has been withdrawn from the old coldkey and recycled + let balance_after = SubtensorModule::get_coldkey_balance(&who); + assert_eq!(balance_after, balance_before - swap_cost.to_u64()); + // let total_issuance_after = TotalIssuance::::get(); + // TODO: handle panics + // assert_eq!(total_issuance_after, total_issuance_before - swap_cost); + + // Ensure the identity is correctly swapped + assert!(IdentitiesV2::::get(&who).is_none()); + assert_eq!(IdentitiesV2::::get(&new_coldkey), Some(identity)); + + // Ensure the subnet ownerships are correctly swapped + assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); + assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); + + // Ensure the auto stake destinations are correctly swapped + assert!(AutoStakeDestination::::get(who, netuid1).is_none()); + assert!(AutoStakeDestination::::get(who, netuid2).is_none()); + assert_eq!( + AutoStakeDestination::::get(new_coldkey, netuid1), + Some(hotkey1) + ); + assert_eq!( + AutoStakeDestination::::get(new_coldkey, netuid2), + Some(hotkey2) + ); + assert_eq!( + AutoStakeDestinationColdkeys::::get(hotkey1, netuid1), + vec![U256::from(3), U256::from(4), new_coldkey] + ); + assert_eq!( + AutoStakeDestinationColdkeys::::get(hotkey2, netuid2), + vec![U256::from(7), U256::from(8), new_coldkey] + ); + + // Ensure the stake is correctly swapped + }); +} + +#[test] +fn test_swap_coldkey_announced_preserves_new_coldkey_identity() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + + let old_identity = ChainIdentityV2 { + name: b"Old identity".to_vec(), + ..Default::default() + }; + IdentitiesV2::::insert(who, old_identity.clone()); + + let new_identity = ChainIdentityV2 { + name: b"New identity".to_vec(), + ..Default::default() + }; + IdentitiesV2::::insert(new_coldkey, new_identity.clone()); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey + )); + + let now = System::block_number(); + let delay = ColdkeySwapScheduleDuration::::get() + 1; + System::run_to_block::(now + delay); + + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.to_u64()); + + assert_ok!(SubtensorModule::swap_coldkey_announced( + RuntimeOrigin::signed(who) + )); + + // Identity is preserved + assert_eq!(IdentitiesV2::::get(who), Some(old_identity)); + assert_eq!(IdentitiesV2::::get(new_coldkey), Some(new_identity)); + }); +} + +#[test] +fn test_swap_coldkey_announced_with_bad_origin_fails() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::none(), new_coldkey), + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::none()), BadOrigin ); assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::root(), new_coldkey), + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::root()), BadOrigin ); }); } +#[test] +fn test_swap_coldkey_announced_without_announcement_fails() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + + assert_noop!( + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who)), + Error::::ColdKeySwapAnnouncementNotFound + ); + }) +} + #[test] fn test_swap_coldkey_announced_too_early_fails() { new_test_ext(1).execute_with(|| { @@ -194,7 +369,7 @@ fn test_swap_coldkey_announced_with_already_associated_coldkey_fails() { } #[test] -fn test_swap_coldkey_announced_using_registered_hotkey_fails() { +fn test_swap_coldkey_announced_with_hotkey_fails() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); @@ -228,7 +403,7 @@ fn test_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); - + let now = System::block_number(); let delay = ColdkeySwapScheduleDuration::::get() + 1; System::run_to_block::(now + delay); @@ -240,25 +415,6 @@ fn test_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { }); } -#[test] -fn test_swap_subnet_owner() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let netuid = NetUid::from(1u16); - - add_network(netuid, 1, 0); - SubnetOwner::::insert(netuid, old_coldkey); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - assert_eq!(SubnetOwner::::get(netuid), new_coldkey); - }); -} - #[test] fn test_swap_total_coldkey_stake() { new_test_ext(1).execute_with(|| { @@ -412,29 +568,6 @@ fn test_swap_with_multiple_hotkeys() { }); } -#[test] -fn test_swap_with_multiple_subnets() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let netuid1 = NetUid::from(1); - let netuid2 = NetUid::from(2); - - add_network(netuid1, 1, 0); - add_network(netuid2, 1, 0); - SubnetOwner::::insert(netuid1, old_coldkey); - SubnetOwner::::insert(netuid2, old_coldkey); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); - assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); - }); -} - // TODO #[test] fn test_swap_with_zero_balance() { @@ -1301,83 +1434,6 @@ fn test_swap_delegated_stake_for_coldkey() { }); } -#[test] -fn test_swap_subnet_owner_for_coldkey() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let netuid1 = NetUid::from(1); - let netuid2 = NetUid::from(2); - - // Initialize SubnetOwner for old_coldkey - add_network(netuid1, 13, 0); - add_network(netuid2, 14, 0); - SubnetOwner::::insert(netuid1, old_coldkey); - SubnetOwner::::insert(netuid2, old_coldkey); - - // Set up TotalNetworks - TotalNetworks::::put(3); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - // Verify the swap - assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); - assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); - }); -} - -#[test] -fn test_do_swap_coldkey_with_subnet_ownership() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let netuid = NetUid::from(1u16); - let stake_amount = 1000; - let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); - - // Setup initial state - add_network(netuid, 13, 0); - register_ok_neuron(netuid, hotkey, old_coldkey, 0); - - // Set TotalNetworks because swap relies on it - crate::TotalNetworks::::set(1); - - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); - SubnetOwner::::insert(netuid, old_coldkey); - - // Populate OwnedHotkeys map - OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - // Verify subnet ownership transfer - assert_eq!(SubnetOwner::::get(netuid), new_coldkey); - }); -} - -#[test] -fn test_coldkey_has_associated_hotkeys() { - new_test_ext(1).execute_with(|| { - let coldkey = U256::from(1); - let hotkey = U256::from(2); - let netuid = NetUid::from(1u16); - - // Setup initial state - add_network(netuid, 13, 0); - register_ok_neuron(netuid, hotkey, coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); - }); -} - #[test] fn test_coldkey_swap_total() { new_test_ext(1).execute_with(|| { @@ -1779,139 +1835,6 @@ fn test_coldkey_delegations() { }); } -#[test] -fn test_schedule_swap_coldkey_execution() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let netuid = NetUid::from(1u16); - let stake_amount = DefaultMinStake::::get().to_u64() * 10; - let reserve = stake_amount * 10; - - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - add_network(netuid, 13, 0); - register_ok_neuron(netuid, hotkey, old_coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 1000000000000000); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey, - netuid, - stake_amount.into() - )); - - // Check initial ownership - assert_eq!( - Owner::::get(hotkey), - old_coldkey, - "Initial ownership check failed" - ); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - // Schedule the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - // Get the scheduled execution block - let current_block = System::block_number(); - let execution_block = current_block + ColdkeySwapScheduleDuration::::get(); - - System::run_to_block::(execution_block - 1); - - let stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - - run_to_block(execution_block); - - // Run on_initialize for the execution block - >::on_initialize(execution_block); - - // Also run Scheduler's on_initialize - as OnInitialize>::on_initialize( - execution_block, - ); - - // Check if the swap has occurred - let new_owner = Owner::::get(hotkey); - assert_eq!( - new_owner, new_coldkey, - "Ownership was not updated as expected" - ); - - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - stake_before_swap, - "Stake was not transferred to new coldkey" - ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), - TaoCurrency::ZERO, - "Old coldkey still has stake" - ); - - // Check for the SwapExecuted event - System::assert_has_event( - Event::ColdkeySwapped { - old_coldkey, - new_coldkey, - swap_cost, - } - .into(), - ); - }); -} - -#[test] -fn test_coldkey_swap_delegate_identity_updated() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - let netuid = NetUid::from(1); - let burn_cost = TaoCurrency::from(10); - let tempo = 1; - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); - mock::setup_reserves(netuid, 1_000_000_000_000.into(), 1_000_000_000_000.into()); - - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(old_coldkey), - netuid, - old_coldkey - )); - - let name: Vec = b"The Third Coolest Identity".to_vec(); - let identity: ChainIdentityV2 = ChainIdentityV2 { - name: name.clone(), - url: vec![], - image: vec![], - github_repo: vec![], - discord: vec![], - description: vec![], - additional: vec![], - }; - - IdentitiesV2::::insert(old_coldkey, identity.clone()); - - assert!(IdentitiesV2::::get(old_coldkey).is_some()); - assert!(IdentitiesV2::::get(new_coldkey).is_none()); - - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - assert!(IdentitiesV2::::get(old_coldkey).is_none()); - assert!(IdentitiesV2::::get(new_coldkey).is_some()); - assert_eq!( - IdentitiesV2::::get(new_coldkey).expect("Expected an Identity"), - identity - ); - }); -} - #[test] fn test_coldkey_swap_no_identity_no_changes() { new_test_ext(1).execute_with(|| { @@ -1946,52 +1869,6 @@ fn test_coldkey_swap_no_identity_no_changes() { }); } -#[test] -fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(3); - let new_coldkey = U256::from(4); - - let netuid = NetUid::from(1); - let burn_cost = TaoCurrency::from(10); - let tempo = 1; - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); - mock::setup_reserves(netuid, 1_000_000_000_000.into(), 1_000_000_000_000.into()); - - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(old_coldkey), - netuid, - old_coldkey - )); - - let name: Vec = b"The Coolest Identity".to_vec(); - let identity: ChainIdentityV2 = ChainIdentityV2 { - name: name.clone(), - url: vec![], - github_repo: vec![], - image: vec![], - discord: vec![], - description: vec![], - additional: vec![], - }; - - IdentitiesV2::::insert(new_coldkey, identity.clone()); - // Ensure the new coldkey does have an identity before the swap - assert!(IdentitiesV2::::get(new_coldkey).is_some()); - assert!(IdentitiesV2::::get(old_coldkey).is_none()); - - // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - // Ensure no identities have been changed - assert!(IdentitiesV2::::get(old_coldkey).is_none()); - assert!(IdentitiesV2::::get(new_coldkey).is_some()); - }); -} - #[test] fn test_coldkey_in_swap_schedule_prevents_funds_usage() { // Testing the signed extension validate function @@ -2294,37 +2171,6 @@ fn test_announced_coldkey_swap_prevents_critical_calls() { }); } -#[test] -fn test_swap_auto_stake_destination_coldkeys() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let netuid = NetUid::from(1u16); - let coldkeys = vec![U256::from(4), U256::from(5), old_coldkey]; - - add_network(netuid, 1, 0); - AutoStakeDestinationColdkeys::::insert(hotkey, netuid, coldkeys.clone()); - AutoStakeDestination::::insert(old_coldkey, netuid, hotkey); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - let new_coldkeys = AutoStakeDestinationColdkeys::::get(hotkey, netuid); - assert!(new_coldkeys.contains(&new_coldkey)); - assert!(!new_coldkeys.contains(&old_coldkey)); - assert_eq!( - AutoStakeDestination::::try_get(old_coldkey, netuid), - Err(()) - ); - assert_eq!( - AutoStakeDestination::::try_get(new_coldkey, netuid), - Ok(hotkey) - ); - }); -} - #[test] #[allow(deprecated)] fn test_swap_coldkey_deprecated() { From 2f1ad59e2f2e7a4c6c7b246bbbb9282ee086242d Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 4 Dec 2025 23:21:56 +0100 Subject: [PATCH 17/62] comprehensive test --- pallets/subtensor/src/tests/swap_coldkey.rs | 429 ++++++++------------ 1 file changed, 167 insertions(+), 262 deletions(-) diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index df6480495d..cba3df5486 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -150,8 +150,36 @@ fn test_swap_coldkey_announced_works() { let new_coldkey = U256::from(2); let hotkey1 = U256::from(1001); let hotkey2 = U256::from(1002); + let hotkey3 = U256::from(1003); + let swap_cost = SubtensorModule::get_key_swap_cost(); + let left_over = 12345; + let min_stake = DefaultMinStake::::get().to_u64(); + let stake1 = min_stake * 10; + let stake2 = min_stake * 20; + let stake3 = min_stake * 30; let now = System::block_number(); + SubtensorModule::add_balance_to_coldkey_account( + &who, + stake1 + stake2 + stake3 + swap_cost.to_u64() + left_over, + ); + + // Announce the coldkey swap + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey, + )); + assert_eq!( + ColdkeySwapAnnouncements::::get(who), + Some((now, new_coldkey)) + ); + + // Run some blocks for the announcement to be past the delay + // WARN: this requires to be done before staking to neurons to avoid + // value mismatch due to coinbase run + let delay = ColdkeySwapScheduleDuration::::get() + 1; + System::run_to_block::(now + delay); + // Setup networks and subnet ownerships let netuid1 = NetUid::from(1); let netuid2 = NetUid::from(2); @@ -160,6 +188,12 @@ fn test_swap_coldkey_announced_works() { SubnetOwner::::insert(netuid1, who); SubnetOwner::::insert(netuid2, who); + // Setup reserves + let reserve1 = (stake1 + stake3) * 10; + let reserve2 = stake2 * 10; + mock::setup_reserves(netuid1, reserve1.into(), reserve1.into()); + mock::setup_reserves(netuid2, reserve2.into(), reserve2.into()); + // Setup auto stake destinations AutoStakeDestination::::insert(who, netuid1, hotkey1); AutoStakeDestination::::insert(who, netuid2, hotkey2); @@ -174,35 +208,55 @@ fn test_swap_coldkey_announced_works() { vec![U256::from(7), U256::from(8), who], ); + // Setup neurons with stake + register_ok_neuron(netuid1, hotkey1, who, 0); + register_ok_neuron(netuid2, hotkey2, who, 0); + register_ok_neuron(netuid1, hotkey3, who, 0); + + let hotkeys = vec![hotkey1, hotkey2, hotkey3]; + assert_eq!(StakingHotkeys::::get(&who), hotkeys); + assert_eq!(OwnedHotkeys::::get(&who), hotkeys); + assert_eq!(Owner::::get(hotkey1), who); + assert_eq!(Owner::::get(hotkey2), who); + assert_eq!(Owner::::get(hotkey3), who); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(who), + hotkey1, + netuid1, + stake1.into() + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(who), + hotkey2, + netuid2, + stake2.into() + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(who), + hotkey3, + netuid1, + stake3.into() + )); + let hk1_alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &who, netuid1); + let hk2_alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey2, &who, netuid2); + let hk3_alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey3, &who, netuid1); + let total_ck_stake = SubtensorModule::get_total_stake_for_coldkey(&who); + // Setup identity let identity = ChainIdentityV2::default(); IdentitiesV2::::insert(who, identity.clone()); assert_eq!(IdentitiesV2::::get(who), Some(identity.clone())); assert!(IdentitiesV2::::get(new_coldkey).is_none()); - // Announce the coldkey swap - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), - new_coldkey, - )); - assert_eq!( - ColdkeySwapAnnouncements::::get(who), - Some((now, new_coldkey)) - ); - - // Run some blocks for the announcement to be past the delay - let delay = ColdkeySwapScheduleDuration::::get() + 1; - System::run_to_block::(now + delay); - - // Add balance to the old coldkey to pay for the swap cost - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.to_u64()); - let balance_before = SubtensorModule::get_coldkey_balance(&who); - // let total_issuance_before = SubtensorModule::get_total_issuance(); + let total_stake_before = SubtensorModule::get_total_stake(); assert_ok!(SubtensorModule::swap_coldkey_announced( - RuntimeOrigin::signed(who), + ::RuntimeOrigin::signed(who) )); // Ensure the announcement has been consumed @@ -210,9 +264,12 @@ fn test_swap_coldkey_announced_works() { // Ensure the cost has been withdrawn from the old coldkey and recycled let balance_after = SubtensorModule::get_coldkey_balance(&who); - assert_eq!(balance_after, balance_before - swap_cost.to_u64()); - // let total_issuance_after = TotalIssuance::::get(); + assert_eq!( + balance_before - swap_cost.to_u64(), + balance_after + left_over + ); // TODO: handle panics + // let total_issuance_after = TotalIssuance::::get(); // assert_eq!(total_issuance_after, total_issuance_before - swap_cost); // Ensure the identity is correctly swapped @@ -243,12 +300,91 @@ fn test_swap_coldkey_announced_works() { vec![U256::from(7), U256::from(8), new_coldkey] ); - // Ensure the stake is correctly swapped + // Ensure the coldkey stake is correctly swapped + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &who, netuid1), + AlphaCurrency::ZERO + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey2, &who, netuid2), + AlphaCurrency::ZERO + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey3, &who, netuid1), + AlphaCurrency::ZERO + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &new_coldkey, + netuid1 + ), + hk1_alpha + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &new_coldkey, + netuid2 + ), + hk2_alpha + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey3, + &new_coldkey, + netuid1 + ), + hk3_alpha + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&who), + TaoCurrency::ZERO + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + total_ck_stake, + ); + + // Ensure the staking hotkeys are correctly swapped + assert!(StakingHotkeys::::get(&who).is_empty()); + assert_eq!(StakingHotkeys::::get(&new_coldkey), hotkeys); + + // Ensure the hotkey ownership is correctly swapped + assert!(OwnedHotkeys::::get(&who).is_empty()); + assert_eq!(OwnedHotkeys::::get(&new_coldkey), hotkeys); + assert_eq!(Owner::::get(hotkey1), new_coldkey); + assert_eq!(Owner::::get(hotkey2), new_coldkey); + assert_eq!(Owner::::get(hotkey3), new_coldkey); + + // Ensure the remaining balance is transferred to the new coldkey + assert_eq!(SubtensorModule::get_coldkey_balance(&who), 0); + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey), + left_over + ); + + // Ensure total stake is unchanged + assert_eq!( + SubtensorModule::get_total_stake(), + total_stake_before, + "Total stake changed unexpectedly" + ); + + // Verify event emission + System::assert_last_event( + Event::ColdkeySwapped { + old_coldkey: who, + new_coldkey, + swap_cost, + } + .into(), + ); }); } #[test] -fn test_swap_coldkey_announced_preserves_new_coldkey_identity() { +fn test_do_swap_coldkey_preserves_new_coldkey_identity() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); @@ -265,21 +401,10 @@ fn test_swap_coldkey_announced_preserves_new_coldkey_identity() { }; IdentitiesV2::::insert(new_coldkey, new_identity.clone()); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), - new_coldkey - )); - - let now = System::block_number(); - let delay = ColdkeySwapScheduleDuration::::get() + 1; - System::run_to_block::(now + delay); - let swap_cost = SubtensorModule::get_key_swap_cost(); SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.to_u64()); - assert_ok!(SubtensorModule::swap_coldkey_announced( - RuntimeOrigin::signed(who) - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&who, &new_coldkey)); // Identity is preserved assert_eq!(IdentitiesV2::::get(who), Some(old_identity)); @@ -464,25 +589,6 @@ fn test_swap_total_coldkey_stake() { }); } -#[test] -fn test_swap_staking_hotkeys() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - - StakingHotkeys::::insert(old_coldkey, vec![hotkey]); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - assert!(StakingHotkeys::::get(old_coldkey).is_empty()); - assert_eq!(StakingHotkeys::::get(new_coldkey), vec![hotkey]); - }); -} - #[test] fn test_swap_hotkey_owners() { new_test_ext(1).execute_with(|| { @@ -892,179 +998,6 @@ fn test_swap_concurrent_modifications() { }); } -#[test] -fn test_do_swap_coldkey_success() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey1 = U256::from(3); - let hotkey2 = U256::from(4); - let netuid = NetUid::from(1u16); - let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; - let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; - let swap_cost = SubtensorModule::get_key_swap_cost(); - let free_balance_old = 12345 + swap_cost.to_u64(); - - let reserve = (stake_amount1 + stake_amount2) * 10; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - // Setup initial state - add_network(netuid, 13, 0); - register_ok_neuron(netuid, hotkey1, old_coldkey, 0); - register_ok_neuron(netuid, hotkey2, old_coldkey, 0); - - // Add balance to old coldkey - SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - stake_amount1 + stake_amount2 + free_balance_old, - ); - - // Log initial state - log::info!( - "Initial total stake: {}", - SubtensorModule::get_total_stake() - ); - log::info!( - "Initial old coldkey stake: {}", - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey) - ); - log::info!( - "Initial new coldkey stake: {}", - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey) - ); - - // Add stake to the neurons - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey1, - netuid, - stake_amount1.into() - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey2, - netuid, - stake_amount2.into() - )); - - // Insert an Identity - let name: Vec = b"The fourth Coolest Identity".to_vec(); - let identity: ChainIdentityV2 = ChainIdentityV2 { - name: name.clone(), - url: vec![], - github_repo: vec![], - image: vec![], - discord: vec![], - description: vec![], - additional: vec![], - }; - - IdentitiesV2::::insert(old_coldkey, identity.clone()); - - assert!(IdentitiesV2::::get(old_coldkey).is_some()); - assert!(IdentitiesV2::::get(new_coldkey).is_none()); - - // Log state after adding stake - log::info!( - "Total stake after adding: {}", - SubtensorModule::get_total_stake() - ); - log::info!( - "Old coldkey stake after adding: {}", - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey) - ); - log::info!( - "New coldkey stake after adding: {}", - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey) - ); - - // Record total stake before swap - let total_stake_before_swap = SubtensorModule::get_total_stake(); - - let hk1_alpha = Alpha::::get((hotkey1, old_coldkey, netuid)); - let hk2_alpha = Alpha::::get((hotkey2, old_coldkey, netuid)); - let total_ck_stake = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - // Log state after swap - log::info!( - "Total stake after swap: {}", - SubtensorModule::get_total_stake() - ); - log::info!( - "Old coldkey stake after swap: {}", - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey) - ); - log::info!( - "New coldkey stake after swap: {}", - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey) - ); - - // Verify the swap - assert_eq!(Owner::::get(hotkey1), new_coldkey); - assert_eq!(Owner::::get(hotkey2), new_coldkey); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - total_ck_stake - ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), - TaoCurrency::ZERO - ); - assert_eq!( - Alpha::::get((hotkey1, new_coldkey, netuid)), - hk1_alpha - ); - assert_eq!( - Alpha::::get((hotkey2, new_coldkey, netuid)), - hk2_alpha - ); - assert!(!Alpha::::contains_key((hotkey1, old_coldkey, netuid))); - assert!(!Alpha::::contains_key((hotkey2, old_coldkey, netuid))); - - // Verify OwnedHotkeys - let new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); - assert!(new_owned_hotkeys.contains(&hotkey1)); - assert!(new_owned_hotkeys.contains(&hotkey2)); - assert_eq!(new_owned_hotkeys.len(), 2); - assert!(!OwnedHotkeys::::contains_key(old_coldkey)); - - // Verify balance transfer - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey), - free_balance_old - swap_cost.to_u64() - ); - assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); - - // Verify total stake remains unchanged - assert_eq!( - SubtensorModule::get_total_stake(), - total_stake_before_swap, - "Total stake changed unexpectedly" - ); - - // Verify identities were swapped - assert!(IdentitiesV2::::get(old_coldkey).is_none()); - assert!(IdentitiesV2::::get(new_coldkey).is_some()); - assert_eq!( - IdentitiesV2::::get(new_coldkey).expect("Expected an Identity"), - identity - ); - - // Verify event emission - System::assert_last_event( - Event::ColdkeySwapped { - old_coldkey, - new_coldkey, - swap_cost, - } - .into(), - ); - }); -} - #[test] fn test_swap_stake_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -1835,40 +1768,6 @@ fn test_coldkey_delegations() { }); } -#[test] -fn test_coldkey_swap_no_identity_no_changes() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - let netuid = NetUid::from(1); - let burn_cost = TaoCurrency::from(10); - let tempo = 1; - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); - mock::setup_reserves(netuid, 1_000_000_000_000.into(), 1_000_000_000_000.into()); - - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(old_coldkey), - netuid, - old_coldkey - )); - - // Ensure the old coldkey does not have an identity before the swap - assert!(IdentitiesV2::::get(old_coldkey).is_none()); - - // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - // Ensure no identities have been changed - assert!(IdentitiesV2::::get(old_coldkey).is_none()); - assert!(IdentitiesV2::::get(new_coldkey).is_none()); - }); -} - #[test] fn test_coldkey_in_swap_schedule_prevents_funds_usage() { // Testing the signed extension validate function @@ -2206,3 +2105,9 @@ fn test_schedule_swap_coldkey_deprecated() { ); }); } + +// TEST STAKING HOTKEY ARE ADDITIVE TO THE EXISTING ONES + +// TEST HOTKEYS OWNERSHIP IS ADDITIVE TO THE EXISTING ONES + +// TEST TRANSFER ROOT CLAIM WITH NEW KEYS + ROOT CASE From 74a0dad6365bd7a3fd6c0c564c98d564d1a49a5a Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 5 Dec 2025 14:37:18 +0100 Subject: [PATCH 18/62] fix extension + tests --- pallets/subtensor/src/tests/swap_coldkey.rs | 889 ++---------------- .../subtensor/src/transaction_extension.rs | 12 +- 2 files changed, 103 insertions(+), 798 deletions(-) diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index cba3df5486..5bb1c600cb 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -175,7 +175,7 @@ fn test_swap_coldkey_announced_works() { ); // Run some blocks for the announcement to be past the delay - // WARN: this requires to be done before staking to neurons to avoid + // WARN: this is required before staking to neurons to avoid // value mismatch due to coinbase run let delay = ColdkeySwapScheduleDuration::::get() + 1; System::run_to_block::(now + delay); @@ -268,9 +268,6 @@ fn test_swap_coldkey_announced_works() { balance_before - swap_cost.to_u64(), balance_after + left_over ); - // TODO: handle panics - // let total_issuance_after = TotalIssuance::::get(); - // assert_eq!(total_issuance_after, total_issuance_before - swap_cost); // Ensure the identity is correctly swapped assert!(IdentitiesV2::::get(&who).is_none()); @@ -540,95 +537,6 @@ fn test_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { }); } -#[test] -fn test_swap_total_coldkey_stake() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let other_coldkey = U256::from(3); - let hotkey = U256::from(4); - let other_hotkey = U256::from(5); - let stake = DefaultMinStake::::get().to_u64() * 10; - - let netuid = NetUid::from(1u16); - add_network(netuid, 1, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake * 2 + 1_000); - register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); - register_ok_neuron(netuid, other_hotkey, other_coldkey, 1001000); - - let reserve = stake * 10; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey, - netuid, - stake.into() - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - other_hotkey, - netuid, - stake.into() - )); - let total_stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), - TaoCurrency::ZERO - ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - total_stake_before_swap - ); - }); -} - -#[test] -fn test_swap_hotkey_owners() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - - Owner::::insert(hotkey, old_coldkey); - OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - assert_eq!(Owner::::get(hotkey), new_coldkey); - assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); - assert_eq!(OwnedHotkeys::::get(new_coldkey), vec![hotkey]); - }); -} - -#[test] -fn test_transfer_remaining_balance() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let balance = 100; - - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, balance); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance); - }); -} - #[test] fn test_swap_with_no_stake() { new_test_ext(1).execute_with(|| { @@ -651,64 +559,6 @@ fn test_swap_with_no_stake() { }); } -#[test] -fn test_swap_with_multiple_hotkeys() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey1 = U256::from(3); - let hotkey2 = U256::from(4); - - OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); - assert_eq!( - OwnedHotkeys::::get(new_coldkey), - vec![hotkey1, hotkey2] - ); - }); -} - -// TODO -#[test] -fn test_swap_with_zero_balance() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - println!( - "old_coldkey balance: {}", - Balances::free_balance(old_coldkey) - ); - println!( - "new_coldkey balance: {}", - Balances::free_balance(new_coldkey) - ); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - println!( - "old_coldkey balance: {}", - Balances::free_balance(old_coldkey) - ); - println!( - "new_coldkey balance: {}", - Balances::free_balance(new_coldkey) - ); - - assert_eq!(Balances::free_balance(old_coldkey), 0); - assert_eq!(Balances::free_balance(new_coldkey), 0); - }); -} - #[test] fn test_swap_with_max_values() { new_test_ext(1).execute_with(|| { @@ -797,79 +647,6 @@ fn test_swap_with_max_values() { }); } -#[test] -fn test_swap_with_non_existent_new_coldkey() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let stake = DefaultMinStake::::get().to_u64() * 10; - let netuid = NetUid::from(1); - - add_network(netuid, 1, 0); - register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); - // Give old coldkey some balance. - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake + 1_000); - - let reserve = stake * 10; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - // Stake to hotkey. - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey, - netuid, - stake.into() - )); - let expected_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &old_coldkey, - netuid, - ); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); - - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), - TaoCurrency::ZERO - ); - - let actual_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &new_coldkey, - netuid, - ); - assert_abs_diff_eq!( - actual_stake, - expected_stake, - epsilon = expected_stake / 1000.into() - ); - }); -} - -#[test] -fn test_swap_with_max_hotkeys() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let max_hotkeys = 1000; - let hotkeys: Vec = (0..max_hotkeys).map(U256::from).collect(); - - OwnedHotkeys::::insert(old_coldkey, hotkeys.clone()); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); - - assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); - assert_eq!(OwnedHotkeys::::get(new_coldkey), hotkeys); - }); -} - #[test] fn test_swap_effect_on_delegated_stake() { new_test_ext(1).execute_with(|| { @@ -927,300 +704,6 @@ fn test_swap_effect_on_delegated_stake() { }); } -// TODO -#[test] -fn test_swap_concurrent_modifications() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let netuid = NetUid::from(1); - let initial_stake = 1_000_000_000_000; - let additional_stake = 500_000_000_000; - - let reserve = (initial_stake + additional_stake) * 1000; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - // Setup initial state - add_network(netuid, 1, 1); - SubtensorModule::add_balance_to_coldkey_account( - &new_coldkey, - initial_stake + additional_stake + 1_000_000, - ); - register_ok_neuron(netuid, hotkey, new_coldkey, 1001000); - - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(new_coldkey), - hotkey, - netuid, - initial_stake.into() - )); - - // Verify initial stake - let stake_before_swap = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &new_coldkey, - netuid, - ); - - // Wait some blocks - step_block(10); - - // Simulate concurrent stake addition - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(new_coldkey), - hotkey, - netuid, - additional_stake.into() - )); - - let stake_with_additional = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &new_coldkey, - netuid, - ); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &new_coldkey, - netuid - ), - stake_with_additional - ); - assert!(stake_with_additional > stake_before_swap); - assert!(!Alpha::::contains_key((hotkey, old_coldkey, netuid))); - }); -} - -#[test] -fn test_swap_stake_for_coldkey() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey1 = U256::from(3); - let hotkey2 = U256::from(4); - let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; - let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; - let stake_amount3 = DefaultMinStake::::get().to_u64() * 30; - - // Setup initial state - // Add a network - let netuid = NetUid::from(1u16); - add_network(netuid, 1, 0); - - // Register hotkeys - register_ok_neuron(netuid, hotkey1, old_coldkey, 0); - register_ok_neuron(netuid, hotkey2, old_coldkey, 0); - // Give some balance to old coldkey - SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - stake_amount1 + stake_amount2 + 1_000_000, - ); - - let reserve = (stake_amount1 + stake_amount2 + stake_amount3) * 10; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - // Stake to hotkeys - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey1, - netuid, - stake_amount1.into() - )); - let expected_stake_alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &old_coldkey, - netuid, - ); - - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey2, - netuid, - stake_amount2.into() - )); - let expected_stake_alpha2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &old_coldkey, - netuid, - ); - - // Insert existing for same hotkey1 - // give new coldkey some balance - SubtensorModule::add_balance_to_coldkey_account(&new_coldkey, stake_amount3 + 1_000_000); - // Stake to hotkey1 - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(new_coldkey), - hotkey1, - netuid, - stake_amount3.into() - )); - let expected_stake_alpha3 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &new_coldkey, - netuid, - ); - - // Record initial values - let initial_total_issuance = SubtensorModule::get_total_issuance(); - let initial_total_stake = SubtensorModule::get_total_stake(); - let initial_total_stake_for_old_coldkey = - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - let initial_total_stake_for_new_coldkey = - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey); - let initial_total_hotkey1_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey1); - let initial_total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - // Verify stake is additive, not replaced - assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - initial_total_stake_for_old_coldkey + initial_total_stake_for_new_coldkey, - epsilon = 2.into() - ); - - // Verify ownership transfer - assert_eq!( - SubtensorModule::get_owned_hotkeys(&new_coldkey), - vec![hotkey1, hotkey2] - ); - assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![]); - - // Verify stake transfer - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &new_coldkey, - netuid - ), - expected_stake_alpha1 + expected_stake_alpha3 - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &new_coldkey, - netuid - ), - expected_stake_alpha2 - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &old_coldkey, - netuid - ), - AlphaCurrency::ZERO - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &old_coldkey, - netuid - ), - AlphaCurrency::ZERO - ); - - // Verify TotalHotkeyStake remains unchanged - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey1), - initial_total_hotkey1_stake - ); - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey2), - initial_total_hotkey2_stake - ); - - // Verify total stake and issuance remain unchanged - assert_eq!( - SubtensorModule::get_total_stake(), - initial_total_stake, - "Total stake changed unexpectedly" - ); - assert_eq!( - SubtensorModule::get_total_issuance(), - initial_total_issuance, - "Total issuance changed unexpectedly" - ); - }); -} - -#[test] -fn test_swap_staking_hotkeys_for_coldkey() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let other_coldkey = U256::from(3); - let hotkey1 = U256::from(4); - let hotkey2 = U256::from(5); - let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; - let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; - - // Setup initial state - // Add a network - let netuid = NetUid::from(1u16); - add_network(netuid, 1, 0); - // Give some balance to old coldkey - SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - stake_amount1 + stake_amount2 + 1_000_000, - ); // Register hotkeys - register_ok_neuron(netuid, hotkey1, old_coldkey, 0); - register_ok_neuron(netuid, hotkey2, other_coldkey, 0); - - let reserve = (stake_amount1 + stake_amount2) * 10; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - // Stake to hotkeys - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey1, - netuid, - stake_amount1.into() - )); - let expected_stake_alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &old_coldkey, - netuid, - ); - - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey2, - netuid, - stake_amount2.into() - )); - let expected_stake_alpha2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &old_coldkey, - netuid, - ); - - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - - // Verify StakingHotkeys transfer - assert_eq!( - StakingHotkeys::::get(new_coldkey), - vec![hotkey1, hotkey2] - ); - assert_eq!(StakingHotkeys::::get(old_coldkey), vec![]); - }); -} - #[test] fn test_swap_delegated_stake_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -1769,307 +1252,121 @@ fn test_coldkey_delegations() { } #[test] -fn test_coldkey_in_swap_schedule_prevents_funds_usage() { - // Testing the signed extension validate function - // correctly filters transactions that attempt to use funds - // while a coldkey swap is scheduled. - +fn test_subtensor_extension_rejects_any_call_that_is_not_swap_coldkey_announced() { new_test_ext(0).execute_with(|| { let netuid = NetUid::from(1); - let version_key: u64 = 0; - let coldkey = U256::from(0); + let who = U256::from(0); let new_coldkey = U256::from(1); - let hotkey: U256 = U256::from(2); // Add the hotkey field - assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! - - let stake = 100_000_000_000; - let reserve = stake * 100; + let hotkey = U256::from(2); + let stake = DefaultMinStake::::get().to_u64(); + assert_ne!(hotkey, who); + // Setup reserves + let reserve = stake * 10; mock::setup_reserves(netuid, reserve.into(), reserve.into()); - let who = coldkey; // The coldkey signs this transaction - - // Disallowed transactions are - // - add_stake - // - add_stake_limit - // - swap_stake - // - swap_stake_limit - // - move_stake - // - transfer_stake - // - balances.transfer_all - // - balances.transfer_allow_death - // - balances.transfer_keep_alive - - // Allowed transactions are: - // - remove_stake - // - remove_stake_limit - // others... - - // Create netuid + // Setup network and neuron add_network(netuid, 1, 0); - // Register the hotkey - SubtensorModule::append_neuron(netuid, &hotkey, 0); - crate::Owner::::insert(hotkey, coldkey); + register_ok_neuron(netuid, hotkey, who, 0); SubtensorModule::add_balance_to_coldkey_account(&who, u64::MAX); - // Set the minimum stake to 0. - SubtensorModule::set_stake_threshold(0); - // Add stake to the hotkey - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(who), - hotkey, - netuid, - stake.into() - )); - // Schedule the coldkey for a swap assert_ok!(SubtensorModule::announce_coldkey_swap( ::RuntimeOrigin::signed(who), new_coldkey, )); - assert!(ColdkeySwapAnnouncements::::contains_key(who)); - // Try each call - - // Add stake - let call = RuntimeCall::SubtensorModule(SubtensorCall::add_stake { - hotkey, - netuid, - amount_staked: stake.into(), - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - // Should fail - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Add stake limit - let call = RuntimeCall::SubtensorModule(SubtensorCall::add_stake_limit { - hotkey, - netuid, - amount_staked: stake.into(), - limit_price: stake.into(), - allow_partial: false, - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Swap stake - let call = RuntimeCall::SubtensorModule(SubtensorCall::swap_stake { - hotkey, - origin_netuid: netuid, - destination_netuid: netuid, - alpha_amount: stake.into(), - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Swap stake limit - let call = RuntimeCall::SubtensorModule(SubtensorCall::swap_stake_limit { - hotkey, - origin_netuid: netuid, - destination_netuid: netuid, - alpha_amount: stake.into(), - limit_price: stake.into(), - allow_partial: false, - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Move stake - let call = RuntimeCall::SubtensorModule(SubtensorCall::move_stake { - origin_hotkey: hotkey, - destination_hotkey: hotkey, - origin_netuid: netuid, - destination_netuid: netuid, - alpha_amount: stake.into(), - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Transfer stake - let call = RuntimeCall::SubtensorModule(SubtensorCall::transfer_stake { - destination_coldkey: new_coldkey, - hotkey, - origin_netuid: netuid, - destination_netuid: netuid, - alpha_amount: stake.into(), - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Transfer all - let call = RuntimeCall::Balances(BalancesCall::transfer_all { - dest: new_coldkey, - keep_alive: false, - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Transfer keep alive - let call = RuntimeCall::Balances(BalancesCall::transfer_keep_alive { - dest: new_coldkey, - value: 100_000_000_000, - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Transfer allow death - let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: new_coldkey, - value: 100_000_000_000, - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Burned register - let call = RuntimeCall::SubtensorModule(SubtensorCall::burned_register { netuid, hotkey }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid); - - // Remove stake - let call = RuntimeCall::SubtensorModule(SubtensorCall::remove_stake { - hotkey, - netuid, - amount_unstaked: (DefaultMinStake::::get().to_u64() * 2).into(), - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Remove stake limit - let call = RuntimeCall::SubtensorModule(SubtensorCall::remove_stake_limit { - hotkey, - netuid, - amount_unstaked: (DefaultMinStake::::get().to_u64() * 2).into(), - limit_price: 123456789.into(), // should be low enough - allow_partial: true, - }); - let info = call.get_dispatch_info(); - let ext = SubtensorTransactionExtension::::new(); - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), - CustomTransactionError::ColdkeySwapAnnounced - ); - - // Announce coldkey swap should succeed - let call = - RuntimeCall::SubtensorModule(SubtensorCall::announce_coldkey_swap { new_coldkey }); + let forbidden_calls: Vec = vec![ + RuntimeCall::SubtensorModule(SubtensorCall::dissolve_network { + netuid, + coldkey: who, + }), + RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey, + netuid, + amount_staked: stake.into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::add_stake_limit { + hotkey, + netuid, + amount_staked: stake.into(), + limit_price: stake.into(), + allow_partial: false, + }), + RuntimeCall::SubtensorModule(SubtensorCall::swap_stake { + hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::swap_stake_limit { + hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + limit_price: stake.into(), + allow_partial: false, + }), + RuntimeCall::SubtensorModule(SubtensorCall::move_stake { + origin_hotkey: hotkey, + destination_hotkey: hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::transfer_stake { + destination_coldkey: new_coldkey, + hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::remove_stake { + hotkey, + netuid, + amount_unstaked: (DefaultMinStake::::get().to_u64() * 2).into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::remove_stake_limit { + hotkey, + netuid, + amount_unstaked: (stake * 2).into(), + limit_price: 123456789.into(), + allow_partial: true, + }), + RuntimeCall::SubtensorModule(SubtensorCall::burned_register { netuid, hotkey }), + RuntimeCall::Balances(BalancesCall::transfer_all { + dest: new_coldkey, + keep_alive: false, + }), + RuntimeCall::Balances(BalancesCall::transfer_keep_alive { + dest: new_coldkey, + value: 100_000_000_000, + }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: new_coldkey, + value: 100_000_000_000, + }), + ]; + + for call in forbidden_calls { + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced + ); + } + + // Swap coldkey announced should succeed + let call = RuntimeCall::SubtensorModule(SubtensorCall::swap_coldkey_announced {}); let info = call.get_dispatch_info(); let ext = SubtensorTransactionExtension::::new(); assert_ok!(ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0)); }); } -#[test] -fn test_announced_coldkey_swap_prevents_critical_calls() { - // Testing the signed extension validate function - // correctly filters transactions that are critical - // while a coldkey swap is announced. - - new_test_ext(0).execute_with(|| { - let netuid = NetUid::from(1); - let version_key: u64 = 0; - let coldkey = U256::from(0); - let new_coldkey = U256::from(1); - let hotkey: U256 = U256::from(2); // Add the hotkey field - assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! - let stake = 100_000_000_000; - let reserve = stake * 10; - - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - - let who = coldkey; // The coldkey signs this transaction - - // Disallowed transactions are - // - dissolve_network - - // Create netuid - add_network(netuid, 1, 0); - // Register the hotkey - SubtensorModule::append_neuron(netuid, &hotkey, 0); - crate::Owner::::insert(hotkey, coldkey); - - SubtensorModule::add_balance_to_coldkey_account(&who, u64::MAX); - - // Set the minimum stake to 0. - SubtensorModule::set_stake_threshold(0); - // Add stake to the hotkey - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(who), - hotkey, - netuid, - stake.into() - )); - - // Schedule the coldkey for a swap - assert_ok!(SubtensorModule::announce_coldkey_swap( - ::RuntimeOrigin::signed(who), - new_coldkey, - )); - - assert!(ColdkeySwapAnnouncements::::contains_key(who)); - - // Try each call - - // Dissolve network - let ext = SubtensorTransactionExtension::::new(); - let call = - RuntimeCall::SubtensorModule(SubtensorCall::dissolve_network { netuid, coldkey }); - let info = call.get_dispatch_info(); - - assert_noop!( - ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0,), - CustomTransactionError::ColdkeySwapAnnounced - ); - }); -} - #[test] #[allow(deprecated)] fn test_swap_coldkey_deprecated() { diff --git a/pallets/subtensor/src/transaction_extension.rs b/pallets/subtensor/src/transaction_extension.rs index 7a9e226eb7..32eb057f98 100644 --- a/pallets/subtensor/src/transaction_extension.rs +++ b/pallets/subtensor/src/transaction_extension.rs @@ -1,4 +1,7 @@ -use crate::{BalancesCall, Call, Config, CustomTransactionError, Error, Pallet, TransactionType}; +use crate::{ + BalancesCall, Call, ColdkeySwapAnnouncements, Config, CustomTransactionError, Error, Pallet, + TransactionType, +}; use codec::{Decode, DecodeWithMemTracking, Encode}; use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; use frame_support::traits::IsSubType; @@ -113,7 +116,12 @@ where }; // Ensure the origin coldkey is not announced for a swap. - if !matches!(call.is_sub_type(), Some(Call::announce_coldkey_swap { .. })) { + if ColdkeySwapAnnouncements::::contains_key(who) + && !matches!( + call.is_sub_type(), + Some(Call::swap_coldkey_announced { .. }) + ) + { return Err(CustomTransactionError::ColdkeySwapAnnounced.into()); } From e6d6925ca8a4554be27e10c412f612ab04a8f252 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 5 Dec 2025 14:42:28 +0100 Subject: [PATCH 19/62] rename tests --- pallets/subtensor/src/tests/swap_coldkey.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 5bb1c600cb..c13002a9ac 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -521,7 +521,7 @@ fn test_swap_coldkey_announced_with_hotkey_fails() { } #[test] -fn test_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { +fn test_do_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); @@ -538,7 +538,7 @@ fn test_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { } #[test] -fn test_swap_with_no_stake() { +fn test_do_swap_coldkey_with_no_stake() { new_test_ext(1).execute_with(|| { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); @@ -560,7 +560,7 @@ fn test_swap_with_no_stake() { } #[test] -fn test_swap_with_max_values() { +fn test_do_swap_coldkey_with_max_values() { new_test_ext(1).execute_with(|| { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); @@ -648,7 +648,7 @@ fn test_swap_with_max_values() { } #[test] -fn test_swap_effect_on_delegated_stake() { +fn test_do_swap_coldkey_effect_on_delegated_stake() { new_test_ext(1).execute_with(|| { let subnet_owner_coldkey = U256::from(1001); let subnet_owner_hotkey = U256::from(1002); @@ -1167,7 +1167,7 @@ fn test_coldkey_swap_total() { } #[test] -fn test_coldkey_delegations() { +fn test_do_swap_coldkey_effect_on_delegations() { new_test_ext(1).execute_with(|| { let new_coldkey = U256::from(0); let owner = U256::from(1); From 760f4d37bd31c059535dfe44ad45545c9d445a7a Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 5 Dec 2025 15:39:56 +0100 Subject: [PATCH 20/62] use hash instead of coldkey during announcement --- pallets/subtensor/src/macros/dispatches.rs | 24 ++++-- pallets/subtensor/src/macros/errors.rs | 2 + pallets/subtensor/src/macros/events.rs | 4 +- pallets/subtensor/src/tests/swap_coldkey.rs | 95 ++++++++++++++------- 4 files changed, 85 insertions(+), 40 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index be92fec5a0..05ef213056 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -9,7 +9,7 @@ mod dispatches { use frame_support::traits::schedule::v3::Anon as ScheduleAnon; use frame_system::pallet_prelude::BlockNumberFor; use sp_core::ecdsa::Signature; - use sp_runtime::Percent; + use sp_runtime::{Percent, traits::Hash}; use crate::MAX_CRV3_COMMIT_SIZE_BYTES; use crate::MAX_NUM_ROOT_CLAIMS; @@ -2335,12 +2335,13 @@ mod dispatches { Ok(()) } - /// Announces a coldkey swap. This is required before the coldkey swap can be performed after the delay period. + /// Announces a coldkey swap using coldkey hash. + /// This is required before the coldkey swap can be performed after the delay period. #[pallet::call_index(125)] #[pallet::weight(Weight::zero())] pub fn announce_coldkey_swap( origin: OriginFor, - new_coldkey: T::AccountId, + new_coldkey_hash: T::Hash, ) -> DispatchResult { let who = ensure_signed(origin)?; let now = >::block_number(); @@ -2354,25 +2355,34 @@ mod dispatches { ); } - ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey.clone())); + ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash.clone())); Self::deposit_event(Event::ColdkeySwapAnnounced { who: who.clone(), - new_coldkey: new_coldkey.clone(), + new_coldkey_hash: new_coldkey_hash.clone(), block_number: now, }); Ok(()) } /// Performs a coldkey swap iff an announcement has been made. + /// The provided new coldkey must match the announced coldkey hash. #[pallet::call_index(126)] #[pallet::weight(Weight::zero())] - pub fn swap_coldkey_announced(origin: OriginFor) -> DispatchResult { + pub fn swap_coldkey_announced( + origin: OriginFor, + new_coldkey: T::AccountId, + ) -> DispatchResult { let who = ensure_signed(origin)?; - let (when, new_coldkey) = ColdkeySwapAnnouncements::::take(who.clone()) + let (when, new_coldkey_hash) = ColdkeySwapAnnouncements::::take(who.clone()) .ok_or(Error::::ColdKeySwapAnnouncementNotFound)?; + ensure!( + new_coldkey_hash == T::Hashing::hash_of(&new_coldkey), + Error::::AnnouncedColdkeyHashDoesNotMatch + ); + let now = >::block_number(); let delay = ColdkeySwapScheduleDuration::::get(); ensure!(now > when + delay, Error::::ColdKeySwapTooEarly); diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 49bbdfd2f8..710d9c4487 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -154,6 +154,8 @@ mod errors { ColdKeySwapTooEarly, /// Coldkey swap reannounced too early. ColdKeySwapReannouncedTooEarly, + /// The announced coldkey hash does not match the new coldkey hash. + AnnouncedColdkeyHashDoesNotMatch, /// New coldkey is hotkey NewColdKeyIsHotkey, /// Childkey take is invalid. diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 3efc74e413..c2f34daba9 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -174,8 +174,8 @@ mod events { ColdkeySwapAnnounced { /// The account ID of the coldkey that made the announcement. who: T::AccountId, - /// The account ID of the new coldkey. - new_coldkey: T::AccountId, + /// The hash of the new coldkey. + new_coldkey_hash: T::Hash, /// The block number the announcement was made. block_number: BlockNumberFor, }, diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index c13002a9ac..e529c644ea 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -16,6 +16,7 @@ use frame_support::traits::schedule::v3::Named as ScheduleNamed; use frame_support::{assert_err, assert_noop, assert_ok}; use frame_system::{Config, RawOrigin}; use sp_core::{Get, H256, U256}; +use sp_runtime::traits::Hash; use sp_runtime::traits::{DispatchInfoOf, DispatchTransaction, TransactionExtension}; use sp_runtime::{DispatchError, traits::TxBaseImplication}; use substrate_fixed::types::U96F32; @@ -33,24 +34,25 @@ fn test_announce_coldkey_swap_works() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), - new_coldkey, + new_coldkey_hash, )); let now = System::block_number(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who.clone(), (now, new_coldkey))] + vec![(who.clone(), (now, new_coldkey_hash))] ); assert_eq!( last_event(), RuntimeEvent::SubtensorModule(Event::ColdkeySwapAnnounced { who, - new_coldkey, + new_coldkey_hash, block_number: now, }) ); @@ -62,19 +64,21 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); let new_coldkey_2 = U256::from(3); + let new_coldkey_2_hash = ::Hashing::hash_of(&new_coldkey_2); assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), - new_coldkey, + new_coldkey_hash, )); let now = System::block_number(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who.clone(), (now, new_coldkey))] + vec![(who.clone(), (now, new_coldkey_hash))] ); let delay = ColdkeySwapScheduleDuration::::get() + 1; @@ -82,13 +86,13 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), - new_coldkey_2, + new_coldkey_2_hash, )); let now = System::block_number(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who.clone(), (now, new_coldkey_2))] + vec![(who.clone(), (now, new_coldkey_2_hash))] ); }); } @@ -97,14 +101,15 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { fn test_announce_coldkey_swap_with_bad_origin_fails() { new_test_ext(1).execute_with(|| { let new_coldkey = U256::from(1); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::none(), new_coldkey), + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::none(), new_coldkey_hash), BadOrigin ); assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::root(), new_coldkey), + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::root(), new_coldkey_hash), BadOrigin ); }); @@ -115,19 +120,21 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); let new_coldkey_2 = U256::from(3); + let new_coldkey_2_hash = ::Hashing::hash_of(&new_coldkey_2); assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), - new_coldkey, + new_coldkey_hash, )); let now = System::block_number(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who.clone(), (now, new_coldkey))] + vec![(who.clone(), (now, new_coldkey_hash))] ); let unmet_delay = ColdkeySwapScheduleDuration::::get(); @@ -136,7 +143,7 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() assert_noop!( SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), - new_coldkey_2, + new_coldkey_2_hash, ), Error::::ColdKeySwapReannouncedTooEarly ); @@ -148,6 +155,7 @@ fn test_swap_coldkey_announced_works() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); let hotkey1 = U256::from(1001); let hotkey2 = U256::from(1002); let hotkey3 = U256::from(1003); @@ -167,11 +175,11 @@ fn test_swap_coldkey_announced_works() { // Announce the coldkey swap assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), - new_coldkey, + new_coldkey_hash, )); assert_eq!( - ColdkeySwapAnnouncements::::get(who), - Some((now, new_coldkey)) + ColdkeySwapAnnouncements::::iter().collect::>(), + vec![(who.clone(), (now, new_coldkey_hash))] ); // Run some blocks for the announcement to be past the delay @@ -256,7 +264,8 @@ fn test_swap_coldkey_announced_works() { let total_stake_before = SubtensorModule::get_total_stake(); assert_ok!(SubtensorModule::swap_coldkey_announced( - ::RuntimeOrigin::signed(who) + ::RuntimeOrigin::signed(who), + new_coldkey )); // Ensure the announcement has been consumed @@ -416,12 +425,12 @@ fn test_swap_coldkey_announced_with_bad_origin_fails() { let new_coldkey = U256::from(2); assert_noop!( - SubtensorModule::swap_coldkey_announced(RuntimeOrigin::none()), + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::none(), new_coldkey), BadOrigin ); assert_noop!( - SubtensorModule::swap_coldkey_announced(RuntimeOrigin::root()), + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::root(), new_coldkey), BadOrigin ); }); @@ -434,26 +443,48 @@ fn test_swap_coldkey_announced_without_announcement_fails() { let new_coldkey = U256::from(2); assert_noop!( - SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who)), + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who), new_coldkey), Error::::ColdKeySwapAnnouncementNotFound ); }) } +#[test] +fn test_swap_coldkey_announced_with_mismatched_coldkey_hash_fails() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let other_coldkey = U256::from(3); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who.clone()), + new_coldkey_hash, + )); + + assert_noop!( + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who), other_coldkey), + Error::::AnnouncedColdkeyHashDoesNotMatch + ); + }) +} + #[test] fn test_swap_coldkey_announced_too_early_fails() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), - new_coldkey, + new_coldkey_hash, )); assert_noop!( SubtensorModule::swap_coldkey_announced( - ::RuntimeOrigin::signed(who) + ::RuntimeOrigin::signed(who), + new_coldkey ), Error::::ColdKeySwapTooEarly ); @@ -465,11 +496,12 @@ fn test_swap_coldkey_announced_with_already_associated_coldkey_fails() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); let hotkey = U256::from(3); assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), - new_coldkey, + new_coldkey_hash, )); let now = System::block_number(); @@ -483,7 +515,8 @@ fn test_swap_coldkey_announced_with_already_associated_coldkey_fails() { assert_noop!( SubtensorModule::swap_coldkey_announced( - ::RuntimeOrigin::signed(who) + ::RuntimeOrigin::signed(who), + new_coldkey ), Error::::ColdKeyAlreadyAssociated ); @@ -496,10 +529,11 @@ fn test_swap_coldkey_announced_with_hotkey_fails() { let who = U256::from(1); let new_coldkey = U256::from(2); let hotkey = U256::from(3); + let hotkey_hash = ::Hashing::hash_of(&hotkey); assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who.clone()), - hotkey.clone(), + hotkey_hash, )); let now = System::block_number(); @@ -513,7 +547,8 @@ fn test_swap_coldkey_announced_with_hotkey_fails() { assert_noop!( SubtensorModule::swap_coldkey_announced( - ::RuntimeOrigin::signed(who) + ::RuntimeOrigin::signed(who), + hotkey ), Error::::NewColdKeyIsHotkey ); @@ -1257,6 +1292,7 @@ fn test_subtensor_extension_rejects_any_call_that_is_not_swap_coldkey_announced( let netuid = NetUid::from(1); let who = U256::from(0); let new_coldkey = U256::from(1); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); let hotkey = U256::from(2); let stake = DefaultMinStake::::get().to_u64(); assert_ne!(hotkey, who); @@ -1274,7 +1310,7 @@ fn test_subtensor_extension_rejects_any_call_that_is_not_swap_coldkey_announced( // Schedule the coldkey for a swap assert_ok!(SubtensorModule::announce_coldkey_swap( ::RuntimeOrigin::signed(who), - new_coldkey, + new_coldkey_hash, )); assert!(ColdkeySwapAnnouncements::::contains_key(who)); @@ -1360,7 +1396,8 @@ fn test_subtensor_extension_rejects_any_call_that_is_not_swap_coldkey_announced( } // Swap coldkey announced should succeed - let call = RuntimeCall::SubtensorModule(SubtensorCall::swap_coldkey_announced {}); + let call = + RuntimeCall::SubtensorModule(SubtensorCall::swap_coldkey_announced { new_coldkey }); let info = call.get_dispatch_info(); let ext = SubtensorTransactionExtension::::new(); assert_ok!(ext.dispatch_transaction(RuntimeOrigin::signed(who).into(), call, &info, 0, 0)); @@ -1403,8 +1440,4 @@ fn test_schedule_swap_coldkey_deprecated() { }); } -// TEST STAKING HOTKEY ARE ADDITIVE TO THE EXISTING ONES - -// TEST HOTKEYS OWNERSHIP IS ADDITIVE TO THE EXISTING ONES - // TEST TRANSFER ROOT CLAIM WITH NEW KEYS + ROOT CASE From 07f5e7258bd1da099f2c45d144e6a4a6a33ce3d4 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 5 Dec 2025 19:09:15 +0100 Subject: [PATCH 21/62] remove unused RescheduleDuration --- chain-extensions/src/mock.rs | 3 --- pallets/admin-utils/src/tests/mock.rs | 5 ----- pallets/subtensor/src/macros/config.rs | 3 --- pallets/subtensor/src/tests/mock.rs | 3 --- pallets/transaction-fee/src/tests/mock.rs | 5 ----- runtime/src/lib.rs | 7 +------ 6 files changed, 1 insertion(+), 25 deletions(-) diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index 98ea096199..edb9245fa7 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -325,9 +325,7 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days - pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -398,7 +396,6 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 0140808baa..6e587f0683 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -131,17 +131,13 @@ parameter_types! { pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. - // pub const InitialSubnetLimit: u16 = 10; // (DEPRECATED) pub const InitialNetworkRateLimit: u64 = 0; pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - // pub const InitialHotkeyEmissionTempo: u64 = 1; // (DEPRECATED) - // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days - pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -211,7 +207,6 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = (); type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index a735bde1e1..e08dffa073 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -221,9 +221,6 @@ mod config { /// Coldkey swap schedule duartion. #[pallet::constant] type InitialColdkeySwapScheduleDuration: Get>; - /// Coldkey swap reschedule duration. - #[pallet::constant] - type InitialColdkeySwapRescheduleDuration: Get>; /// Dissolve network schedule duration #[pallet::constant] type InitialDissolveNetworkScheduleDuration: Get>; diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 090fcf8f75..2a064fbbdc 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -212,9 +212,7 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days - pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -285,7 +283,6 @@ impl crate::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index ee5b1693ba..57f0b4cbda 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -196,17 +196,13 @@ parameter_types! { pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. - // pub const InitialSubnetLimit: u16 = 10; // (DEPRECATED) pub const InitialNetworkRateLimit: u64 = 0; pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - // pub const InitialHotkeyEmissionTempo: u64 = 1; // (DEPRECATED) - // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days - pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -276,7 +272,6 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = (); type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 391036e380..5855c1063b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1039,7 +1039,6 @@ parameter_types! { pub const SubtensorInitialMinAllowedUids: u16 = 64; pub const SubtensorInitialMinLockCost: u64 = 1_000_000_000_000; // 1000 TAO pub const SubtensorInitialSubnetOwnerCut: u16 = 11_796; // 18 percent - // pub const SubtensorInitialSubnetLimit: u16 = 12; // (DEPRECATED) pub const SubtensorInitialNetworkLockReductionInterval: u64 = 14 * 7200; pub const SubtensorInitialNetworkRateLimit: u64 = 7200; pub const SubtensorInitialKeySwapCost: u64 = 100_000_000; // 0.1 TAO @@ -1047,14 +1046,11 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - // pub const SubtensorInitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days - pub const InitialColdkeySwapRescheduleDuration: BlockNumber = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks - // 7 * 24 * 60 * 60 / 12 = 7 days - pub const DurationOfStartCall: u64 = prod_or_fast!(7 * 24 * 60 * 60 / 12, 10); + pub const DurationOfStartCall: u64 = prod_or_fast!(7 * 24 * 60 * 60 / 12, 10); // 7 days pub const SubtensorInitialKeySwapOnSubnetCost: u64 = 1_000_000; // 0.001 TAO pub const HotkeySwapOnSubnetInterval : BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const LeaseDividendsDistributionInterval: BlockNumber = 100; // 100 blocks @@ -1121,7 +1117,6 @@ impl pallet_subtensor::Config for Runtime { type InitialTaoWeight = SubtensorInitialTaoWeight; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall; From a962bcdbb854880f674e36008dc90cb9d17e18bc Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 5 Dec 2025 19:37:15 +0100 Subject: [PATCH 22/62] fix TransactionError naming --- pallets/subtensor/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d19ff35ebe..b05621bcef 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2433,7 +2433,7 @@ pub mod pallet { #[derive(Debug, PartialEq)] pub enum CustomTransactionError { - ColdkeyInSwapSchedule, + ColdkeySwapAnnounced, StakeAmountTooLow, BalanceTooLow, SubnetNotExists, @@ -2460,7 +2460,7 @@ pub enum CustomTransactionError { impl From for u8 { fn from(variant: CustomTransactionError) -> u8 { match variant { - CustomTransactionError::ColdkeyInSwapSchedule => 0, + CustomTransactionError::ColdkeySwapAnnounced => 0, CustomTransactionError::StakeAmountTooLow => 1, CustomTransactionError::BalanceTooLow => 2, CustomTransactionError::SubnetNotExists => 3, From 688ed6890e7e811b1477ce5d17ca70c4e24fee7e Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 5 Dec 2025 19:37:45 +0100 Subject: [PATCH 23/62] announcements are hash of coldkey instead of raw coldkey --- pallets/subtensor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b05621bcef..ee13699223 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1373,7 +1373,7 @@ pub mod pallet { /// to the block number the announcement was made and the new coldkey. #[pallet::storage] pub type ColdkeySwapAnnouncements = - StorageMap<_, Twox64Concat, T::AccountId, (BlockNumberFor, T::AccountId), OptionQuery>; + StorageMap<_, Twox64Concat, T::AccountId, (BlockNumberFor, T::Hash), OptionQuery>; /// --- DMAP ( hot, netuid ) --> alpha | Returns the total amount of alpha a hotkey owns. #[pallet::storage] From dfd326d340c02132fc8f0f00235be4826738c6cd Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 5 Dec 2025 23:24:08 +0100 Subject: [PATCH 24/62] rename config parameters/admin dispatches --- chain-extensions/src/mock.rs | 4 +- pallets/admin-utils/src/benchmarking.rs | 2 +- pallets/admin-utils/src/lib.rs | 57 +++++++-------------- pallets/admin-utils/src/tests/mock.rs | 4 +- pallets/admin-utils/src/tests/mod.rs | 8 +-- pallets/subtensor/src/lib.rs | 45 +++------------- pallets/subtensor/src/macros/config.rs | 2 +- pallets/subtensor/src/macros/dispatches.rs | 4 +- pallets/subtensor/src/macros/events.rs | 6 +-- pallets/subtensor/src/tests/mock.rs | 4 +- pallets/subtensor/src/tests/swap_coldkey.rs | 14 ++--- pallets/subtensor/src/utils/misc.rs | 16 ++---- pallets/transaction-fee/src/tests/mock.rs | 4 +- runtime/src/lib.rs | 4 +- 14 files changed, 57 insertions(+), 117 deletions(-) diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index edb9245fa7..ee1b3d0099 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -325,7 +325,7 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialColdKeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -395,7 +395,7 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = Preimage; - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 08589e530b..28f2b1d75d 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -480,7 +480,7 @@ mod benchmarks { } #[benchmark] - fn sudo_set_coldkey_swap_schedule_duration() { + fn sudo_set_coldkey_swap_announcement_delay() { #[extrinsic_call] _(RawOrigin::Root, 100u32.into()); } diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index de6ac5825b..a2967ac499 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1348,44 +1348,6 @@ pub mod pallet { res } - /// Sets the duration of the coldkey swap schedule. - /// - /// This extrinsic allows the root account to set the duration for the coldkey swap schedule. - /// The coldkey swap schedule determines how long it takes for a coldkey swap operation to complete. - /// - /// # Arguments - /// * `origin` - The origin of the call, which must be the root account. - /// * `duration` - The new duration for the coldkey swap schedule, in number of blocks. - /// - /// # Errors - /// * `BadOrigin` - If the caller is not the root account. - /// - /// # Weight - /// Weight is handled by the `#[pallet::weight]` attribute. - #[pallet::call_index(54)] - #[pallet::weight(( - Weight::from_parts(5_000_000, 0) - .saturating_add(T::DbWeight::get().reads(0_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)), - DispatchClass::Operational, - Pays::Yes - ))] - pub fn sudo_set_coldkey_swap_schedule_duration( - origin: OriginFor, - duration: BlockNumberFor, - ) -> DispatchResult { - // Ensure the call is made by the root account - ensure_root(origin)?; - - // Set the new duration of schedule coldkey swap - pallet_subtensor::Pallet::::set_coldkey_swap_schedule_duration(duration); - - // Log the change - log::trace!("ColdkeySwapScheduleDurationSet( duration: {duration:?} )"); - - Ok(()) - } - /// Sets the duration of the dissolve network schedule. /// /// This extrinsic allows the root account to set the duration for the dissolve network schedule. @@ -2213,6 +2175,25 @@ pub mod pallet { log::debug!("set_tao_flow_smoothing_factor( {smoothing_factor:?} ) "); Ok(()) } + + /// Sets the announcement delay for coldkey swap. + #[pallet::call_index(84)] + #[pallet::weight(( + Weight::from_parts(5_000_000, 0) + .saturating_add(T::DbWeight::get().reads(0_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn sudo_set_coldkey_swap_announcement_delay( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + ensure_root(origin)?; + pallet_subtensor::Pallet::::set_coldkey_swap_announcement_delay(duration); + log::trace!("ColdkeySwapScheduleDurationSet( duration: {duration:?} )"); + Ok(()) + } } } diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 6e587f0683..e49afe124f 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -137,7 +137,7 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -206,7 +206,7 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = (); - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 1aaefc8f8d..7ca9372385 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1382,7 +1382,7 @@ fn test_sudo_get_set_alpha() { } #[test] -fn test_sudo_set_coldkey_swap_schedule_duration() { +fn test_sudo_set_coldkey_swap_announcement_delay() { new_test_ext().execute_with(|| { // Arrange let root = RuntimeOrigin::root(); @@ -1391,12 +1391,12 @@ fn test_sudo_set_coldkey_swap_schedule_duration() { // Act & Assert: Non-root account should fail assert_noop!( - AdminUtils::sudo_set_coldkey_swap_schedule_duration(non_root, new_duration), + AdminUtils::sudo_set_coldkey_swap_announcement_delay(non_root, new_duration), DispatchError::BadOrigin ); // Act: Root account should succeed - assert_ok!(AdminUtils::sudo_set_coldkey_swap_schedule_duration( + assert_ok!(AdminUtils::sudo_set_coldkey_swap_announcement_delay( root.clone(), new_duration )); @@ -1408,7 +1408,7 @@ fn test_sudo_set_coldkey_swap_schedule_duration() { ); // Act & Assert: Setting the same value again should succeed (idempotent operation) - assert_ok!(AdminUtils::sudo_set_coldkey_swap_schedule_duration( + assert_ok!(AdminUtils::sudo_set_coldkey_swap_announcement_delay( root, new_duration )); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ee13699223..258f97915e 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -943,17 +943,11 @@ pub mod pallet { pub fn DefaultAlphaValues() -> (u16, u16) { (45875, 58982) } - - /// Default value for coldkey swap schedule duration + + /// Default value for coldkey swap announcement delay. #[pallet::type_value] - pub fn DefaultColdkeySwapScheduleDuration() -> BlockNumberFor { - T::InitialColdkeySwapScheduleDuration::get() - } - - /// Default value for coldkey swap reschedule duration - #[pallet::type_value] - pub fn DefaultColdkeySwapRescheduleDuration() -> BlockNumberFor { - T::InitialColdkeySwapRescheduleDuration::get() + pub fn DefaultColdkeySwapAnnouncementDelay() -> BlockNumberFor { + T::InitialColdkeySwapAnnouncementDelay::get() } /// Default value for applying pending items (e.g. childkeys). @@ -1012,15 +1006,6 @@ pub mod pallet { 360 } - /// Default value for coldkey swap scheduled - #[pallet::type_value] - pub fn DefaultColdkeySwapScheduled() -> (BlockNumberFor, T::AccountId) { - #[allow(clippy::expect_used)] - let default_account = T::AccountId::decode(&mut TrailingZeroInput::zeroes()) - .expect("trailing zeroes always produce a valid account ID; qed"); - (BlockNumberFor::::from(0_u32), default_account) - } - /// Default value for setting subnet owner hotkey rate limit #[pallet::type_value] pub fn DefaultSetSNOwnerHotkeyRateLimit() -> u64 { @@ -1072,16 +1057,6 @@ pub mod pallet { pub type OwnerHyperparamRateLimit = StorageValue<_, u16, ValueQuery, DefaultOwnerHyperparamRateLimit>; - /// Duration of coldkey swap schedule before execution - #[pallet::storage] - pub type ColdkeySwapScheduleDuration = - StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapScheduleDuration>; - - /// Duration of coldkey swap reschedule before execution - #[pallet::storage] - pub type ColdkeySwapRescheduleDuration = - StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapRescheduleDuration>; - /// Duration of dissolve network schedule before execution #[pallet::storage] pub type DissolveNetworkScheduleDuration = @@ -1358,16 +1333,10 @@ pub mod pallet { ValueQuery, >; - /// --- DMAP ( cold ) --> (block_expected, new_coldkey), Maps coldkey to the block to swap at and new coldkey. + /// The delay after an announcement before a coldkey swap can be performed. #[pallet::storage] - pub type ColdkeySwapScheduled = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - (BlockNumberFor, T::AccountId), - ValueQuery, - DefaultColdkeySwapScheduled, - >; + pub type ColdkeySwapAnnouncementDelay = + StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapAnnouncementDelay>; /// A map of the coldkey swap announcements from a coldkey /// to the block number the announcement was made and the new coldkey. diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index e08dffa073..6f8b5466ee 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -220,7 +220,7 @@ mod config { // type InitialHotkeyEmissionTempo: Get; /// Coldkey swap schedule duartion. #[pallet::constant] - type InitialColdkeySwapScheduleDuration: Get>; + type InitialColdkeySwapAnnouncementDelay: Get>; /// Dissolve network schedule duration #[pallet::constant] type InitialDissolveNetworkScheduleDuration: Get>; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 05ef213056..178565a8fe 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2347,7 +2347,7 @@ mod dispatches { let now = >::block_number(); if let Some(existing) = ColdkeySwapAnnouncements::::get(who.clone()) { - let delay = ColdkeySwapScheduleDuration::::get(); + let delay = ColdkeySwapAnnouncementDelay::::get(); let when = existing.0; ensure!( now > when + delay, @@ -2384,7 +2384,7 @@ mod dispatches { ); let now = >::block_number(); - let delay = ColdkeySwapScheduleDuration::::get(); + let delay = ColdkeySwapAnnouncementDelay::::get(); ensure!(now > when + delay, Error::::ColdKeySwapTooEarly); Self::do_swap_coldkey(&who, &new_coldkey)?; diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index c2f34daba9..4babd7f0a8 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -225,15 +225,15 @@ mod events { SubnetIdentityRemoved(NetUid), /// A dissolve network extrinsic scheduled. DissolveNetworkScheduled { - /// The account ID schedule the dissolve network extrisnic + /// The account ID schedule the dissolve network extrinsic account: T::AccountId, /// network ID will be dissolved netuid: NetUid, /// extrinsic execution block number execution_block: BlockNumberFor, }, - /// The duration of schedule coldkey swap has been set - ColdkeySwapScheduleDurationSet(BlockNumberFor), + /// The coldkey swap announcement delay has been set. + ColdkeySwapAnnouncementDelaySet(BlockNumberFor), /// The duration of dissolve network has been set DissolveNetworkScheduleDurationSet(BlockNumberFor), /// Commit-reveal v3 weights have been successfully committed. diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 2a064fbbdc..1d17b38cb9 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -212,7 +212,7 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -282,7 +282,7 @@ impl crate::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = Preimage; - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index e529c644ea..367dda4927 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -27,7 +27,7 @@ use super::mock; use super::mock::*; use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; -use crate::{Call, ColdkeySwapScheduleDuration, Error}; +use crate::{Call, Error}; #[test] fn test_announce_coldkey_swap_works() { @@ -81,7 +81,7 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { vec![(who.clone(), (now, new_coldkey_hash))] ); - let delay = ColdkeySwapScheduleDuration::::get() + 1; + let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); assert_ok!(SubtensorModule::announce_coldkey_swap( @@ -137,7 +137,7 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() vec![(who.clone(), (now, new_coldkey_hash))] ); - let unmet_delay = ColdkeySwapScheduleDuration::::get(); + let unmet_delay = ColdkeySwapAnnouncementDelay::::get(); System::run_to_block::(now + unmet_delay); assert_noop!( @@ -185,7 +185,7 @@ fn test_swap_coldkey_announced_works() { // Run some blocks for the announcement to be past the delay // WARN: this is required before staking to neurons to avoid // value mismatch due to coinbase run - let delay = ColdkeySwapScheduleDuration::::get() + 1; + let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); // Setup networks and subnet ownerships @@ -505,7 +505,7 @@ fn test_swap_coldkey_announced_with_already_associated_coldkey_fails() { )); let now = System::block_number(); - let delay = ColdkeySwapScheduleDuration::::get() + 1; + let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); let swap_cost = SubtensorModule::get_key_swap_cost(); @@ -537,7 +537,7 @@ fn test_swap_coldkey_announced_with_hotkey_fails() { )); let now = System::block_number(); - let delay = ColdkeySwapScheduleDuration::::get() + 1; + let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); let swap_cost = SubtensorModule::get_key_swap_cost(); @@ -562,7 +562,7 @@ fn test_do_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { let new_coldkey = U256::from(2); let now = System::block_number(); - let delay = ColdkeySwapScheduleDuration::::get() + 1; + let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); assert_noop!( diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index cf2d55d168..cbf1e3fa8d 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -830,19 +830,9 @@ impl Pallet { TransferToggle::::get(netuid) } - /// Set the duration for coldkey swap - /// - /// # Arguments - /// - /// * `duration` - The blocks for coldkey swap execution. - /// - /// # Effects - /// - /// * Update the ColdkeySwapScheduleDuration storage. - /// * Emits a ColdkeySwapScheduleDurationSet evnet. - pub fn set_coldkey_swap_schedule_duration(duration: BlockNumberFor) { - ColdkeySwapScheduleDuration::::set(duration); - Self::deposit_event(Event::ColdkeySwapScheduleDurationSet(duration)); + pub fn set_coldkey_swap_announcement_delay(duration: BlockNumberFor) { + ColdkeySwapAnnouncementDelay::::set(duration); + Self::deposit_event(Event::ColdkeySwapAnnouncementDelaySet(duration)); } /// Set the duration for dissolve network diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 57f0b4cbda..4ebef59c38 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -202,7 +202,7 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -271,7 +271,7 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = (); - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5855c1063b..637112de29 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1046,7 +1046,7 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialColdkeySwapAnnouncementDelay: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -1116,7 +1116,7 @@ impl pallet_subtensor::Config for Runtime { type Yuma3On = InitialYuma3On; type InitialTaoWeight = SubtensorInitialTaoWeight; type Preimages = Preimage; - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall; From 370db1a6acb74e9ca3b5e57f081ffbbd3a241423 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 8 Dec 2025 11:55:47 +0100 Subject: [PATCH 25/62] added migration + fix old migration --- .../migrate_coldkey_swap_scheduled.rs | 7 -- ...coldkey_swap_scheduled_to_announcements.rs | 91 +++++++++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/tests/migration.rs | 72 ++++++++++++++- pallets/subtensor/src/tests/mock.rs | 2 +- 5 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs diff --git a/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled.rs b/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled.rs index 8854f76387..243d953ac1 100644 --- a/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled.rs +++ b/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled.rs @@ -55,13 +55,6 @@ pub fn migrate_coldkey_swap_scheduled() -> Weight { } } - let default_value = DefaultColdkeySwapScheduled::::get(); - ColdkeySwapScheduled::::translate::<(), _>(|_coldkey: AccountIdOf, _: ()| { - Some((default_value.0, default_value.1.clone())) - }); - // write once for each item in the map, no matter remove or translate - weight.saturating_accrue(T::DbWeight::get().writes(curr_keys.len() as u64)); - // ------------------------------ // Step 2: Mark Migration as Completed // ------------------------------ diff --git a/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs b/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs new file mode 100644 index 0000000000..adaebdc6b9 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs @@ -0,0 +1,91 @@ +use super::*; +use crate::AccountIdOf; +use frame_support::{pallet_prelude::Blake2_128Concat, traits::Get, weights::Weight}; +use frame_system::pallet_prelude::BlockNumberFor; +use scale_info::prelude::string::String; +use sp_io::storage::clear; +use sp_runtime::traits::Hash; + +pub mod deprecated { + use super::*; + use frame_support::storage_alias; + + #[storage_alias] + pub type ColdkeySwapScheduleDuration = + StorageValue, BlockNumberFor, OptionQuery>; + + #[storage_alias] + pub type ColdkeySwapRescheduleDuration = + StorageValue, BlockNumberFor, OptionQuery>; + + #[storage_alias] + pub type ColdkeySwapScheduled = StorageMap< + Pallet, + Blake2_128Concat, + AccountIdOf, + (BlockNumberFor, AccountIdOf), + OptionQuery, + >; +} + +pub fn migrate_coldkey_swap_scheduled_to_announcements() -> Weight { + let migration_name = b"migrate_coldkey_swap_scheduled_to_announcements".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) + ); + + // Remove ColdkeySwapScheduleDuration and ColdkeySwapRescheduleDuration + let pallet_name = twox_128(b"SubtensorModule"); + let storage_name1 = twox_128(b"ColdkeySwapScheduleDuration"); + let storage_name2 = twox_128(b"ColdkeySwapRescheduleDuration"); + clear(&[pallet_name, storage_name1].concat()); + clear(&[pallet_name, storage_name2].concat()); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + + // Migrate the ColdkeySwapScheduled entries to ColdkeySwapAnnouncements entries + let now = >::block_number(); + let scheduled = deprecated::ColdkeySwapScheduled::::iter(); + let delay = ColdkeySwapAnnouncementDelay::::get(); + + for (who, (when, new_coldkey)) in scheduled { + // Only migrate the scheduled coldkey swaps that are in the future + if when > now { + let coldkey_hash = ::Hashing::hash_of(&new_coldkey); + // The announcement should be at the scheduled time - delay to be able to call + // the swap_coldkey_announced call at the old scheduled time + ColdkeySwapAnnouncements::::insert(who, (when - delay, coldkey_hash)); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + + let results = deprecated::ColdkeySwapScheduled::::clear(u32::MAX, None); + weight.saturating_accrue( + T::DbWeight::get().reads_writes(results.loops as u64, results.backend 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.", + String::from_utf8_lossy(&migration_name) + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index 41c1333a89..6aec87cd4d 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -7,6 +7,7 @@ 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_coldkey_swap_scheduled_to_announcements; pub mod migrate_commit_reveal_settings; pub mod migrate_commit_reveal_v2; pub mod migrate_create_root_network; diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index b694459eaa..776f8609e3 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -25,7 +25,7 @@ use pallet_drand::types::RoundNumber; use scale_info::prelude::collections::VecDeque; use sp_core::{H256, U256, crypto::Ss58Codec}; use sp_io::hashing::twox_128; -use sp_runtime::traits::Zero; +use sp_runtime::{traits::Hash, traits::Zero}; use substrate_fixed::types::extra::U2; use substrate_fixed::types::{I96F32, U64F64}; use subtensor_runtime_common::{NetUidStorageIndex, TaoCurrency}; @@ -2724,3 +2724,73 @@ fn test_migrate_reset_unactive_sn_idempotence() { assert_eq!(TotalIssuance::::get(), total_issuance_before); }); } + +#[test] +fn test_migrate_coldkey_swap_scheduled_to_announcements() { + new_test_ext(1000).execute_with(|| { + use crate::migrations::migrate_coldkey_swap_scheduled_to_announcements::*; + let now = frame_system::Pallet::::block_number(); + + // Set the schedule duration and reschedule duration + deprecated::ColdkeySwapScheduleDuration::::set(Some(now + 100)); + deprecated::ColdkeySwapRescheduleDuration::::set(Some(now + 200)); + + // Set some scheduled coldkey swaps + deprecated::ColdkeySwapScheduled::::insert( + U256::from(1), + (now + 100, U256::from(10)), + ); + deprecated::ColdkeySwapScheduled::::insert( + U256::from(2), + (now - 200, U256::from(20)), + ); + deprecated::ColdkeySwapScheduled::::insert( + U256::from(3), + (now + 200, U256::from(30)), + ); + deprecated::ColdkeySwapScheduled::::insert( + U256::from(4), + (now - 400, U256::from(40)), + ); + deprecated::ColdkeySwapScheduled::::insert( + U256::from(5), + (now + 300, U256::from(50)), + ); + + let w = migrate_coldkey_swap_scheduled_to_announcements::(); + + assert!(!w.is_zero(), "weight must be non-zero"); + + // Ensure the deprecated storage is cleared + assert!(!deprecated::ColdkeySwapScheduleDuration::::exists()); + assert!(!deprecated::ColdkeySwapRescheduleDuration::::exists()); + assert_eq!(deprecated::ColdkeySwapScheduled::::iter().count(), 0); + + // Ensure scheduled have been migrated to announcements if not executed yet + // The announcement should be at the scheduled time - delay to be able to call + // the swap_coldkey_announced call at the old scheduled time + let delay = ColdkeySwapAnnouncementDelay::::get(); + assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 3); + assert_eq!( + ColdkeySwapAnnouncements::::get(U256::from(1)), + Some(( + now + 100 - delay, + ::Hashing::hash_of(&U256::from(10)) + )) + ); + assert_eq!( + ColdkeySwapAnnouncements::::get(U256::from(3)), + Some(( + now + 200 - delay, + ::Hashing::hash_of(&U256::from(30)) + )) + ); + assert_eq!( + ColdkeySwapAnnouncements::::get(U256::from(5)), + Some(( + now + 300 - delay, + ::Hashing::hash_of(&U256::from(50)) + )) + ); + }); +} diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 1d17b38cb9..d0830198b9 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -212,7 +212,7 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialColdkeySwapAnnouncementDelay: u64 = 50; pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks From 492b4179dfa14f26a95faecbf216fd906d470d2a Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 8 Dec 2025 11:55:59 +0100 Subject: [PATCH 26/62] cargo fmt --- pallets/subtensor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 258f97915e..2801aa9f16 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -943,7 +943,7 @@ pub mod pallet { pub fn DefaultAlphaValues() -> (u16, u16) { (45875, 58982) } - + /// Default value for coldkey swap announcement delay. #[pallet::type_value] pub fn DefaultColdkeySwapAnnouncementDelay() -> BlockNumberFor { From 1c6fb23611ea19b0c4196885656f7a4aa8301321 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 8 Dec 2025 12:12:21 +0100 Subject: [PATCH 27/62] clippy fix --- chain-extensions/src/mock.rs | 2 +- pallets/admin-utils/src/tests/mod.rs | 17 +++---- pallets/subtensor/src/macros/dispatches.rs | 9 ++-- ...coldkey_swap_scheduled_to_announcements.rs | 4 +- pallets/subtensor/src/swap/swap_coldkey.rs | 18 +++---- pallets/subtensor/src/tests/swap_coldkey.rs | 49 +++++++++---------- 6 files changed, 49 insertions(+), 50 deletions(-) diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index ee1b3d0099..8a38fea2a7 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -325,7 +325,7 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdKeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 7ca9372385..5fcfeed083 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1387,34 +1387,33 @@ fn test_sudo_set_coldkey_swap_announcement_delay() { // Arrange let root = RuntimeOrigin::root(); let non_root = RuntimeOrigin::signed(U256::from(1)); - let new_duration = 100u32.into(); + let new_delay = 100u32.into(); // Act & Assert: Non-root account should fail assert_noop!( - AdminUtils::sudo_set_coldkey_swap_announcement_delay(non_root, new_duration), + AdminUtils::sudo_set_coldkey_swap_announcement_delay(non_root, new_delay), DispatchError::BadOrigin ); // Act: Root account should succeed assert_ok!(AdminUtils::sudo_set_coldkey_swap_announcement_delay( root.clone(), - new_duration + new_delay )); - // Assert: Check if the duration was actually set + // Assert: Check if the delay was actually set assert_eq!( - pallet_subtensor::ColdkeySwapScheduleDuration::::get(), - new_duration + pallet_subtensor::ColdkeySwapAnnouncementDelay::::get(), + new_delay ); // Act & Assert: Setting the same value again should succeed (idempotent operation) assert_ok!(AdminUtils::sudo_set_coldkey_swap_announcement_delay( - root, - new_duration + root, new_delay )); // You might want to check for events here if your pallet emits them - System::assert_last_event(Event::ColdkeySwapScheduleDurationSet(new_duration).into()); + System::assert_last_event(Event::ColdkeySwapAnnouncementDelaySet(new_delay).into()); }); } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 178565a8fe..e4a4b6f106 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -9,7 +9,7 @@ mod dispatches { use frame_support::traits::schedule::v3::Anon as ScheduleAnon; use frame_system::pallet_prelude::BlockNumberFor; use sp_core::ecdsa::Signature; - use sp_runtime::{Percent, traits::Hash}; + use sp_runtime::{Percent, Saturating, traits::Hash}; use crate::MAX_CRV3_COMMIT_SIZE_BYTES; use crate::MAX_NUM_ROOT_CLAIMS; @@ -2350,7 +2350,7 @@ mod dispatches { let delay = ColdkeySwapAnnouncementDelay::::get(); let when = existing.0; ensure!( - now > when + delay, + now > when.saturating_add(delay), Error::::ColdKeySwapReannouncedTooEarly ); } @@ -2385,7 +2385,10 @@ mod dispatches { let now = >::block_number(); let delay = ColdkeySwapAnnouncementDelay::::get(); - ensure!(now > when + delay, Error::::ColdKeySwapTooEarly); + ensure!( + now > when.saturating_add(delay), + Error::::ColdKeySwapTooEarly + ); Self::do_swap_coldkey(&who, &new_coldkey)?; diff --git a/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs b/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs index adaebdc6b9..12fa3f5768 100644 --- a/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs +++ b/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs @@ -4,7 +4,7 @@ use frame_support::{pallet_prelude::Blake2_128Concat, traits::Get, weights::Weig use frame_system::pallet_prelude::BlockNumberFor; use scale_info::prelude::string::String; use sp_io::storage::clear; -use sp_runtime::traits::Hash; +use sp_runtime::{Saturating, traits::Hash}; pub mod deprecated { use super::*; @@ -64,7 +64,7 @@ pub fn migrate_coldkey_swap_scheduled_to_announcements() -> Weight { let coldkey_hash = ::Hashing::hash_of(&new_coldkey); // The announcement should be at the scheduled time - delay to be able to call // the swap_coldkey_announced call at the old scheduled time - ColdkeySwapAnnouncements::::insert(who, (when - delay, coldkey_hash)); + ColdkeySwapAnnouncements::::insert(who, (when.saturating_sub(delay), coldkey_hash)); weight.saturating_accrue(T::DbWeight::get().writes(1)); } weight.saturating_accrue(T::DbWeight::get().reads(1)); diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 491daf9c98..277f912192 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -9,11 +9,11 @@ impl Pallet { new_coldkey: &T::AccountId, ) -> DispatchResult { ensure!( - StakingHotkeys::::get(&new_coldkey).is_empty(), + StakingHotkeys::::get(new_coldkey).is_empty(), Error::::ColdKeyAlreadyAssociated ); ensure!( - !Self::hotkey_account_exists(&new_coldkey), + !Self::hotkey_account_exists(new_coldkey), Error::::NewColdKeyIsHotkey ); @@ -34,21 +34,21 @@ impl Pallet { } for netuid in Self::get_all_subnet_netuids() { - Self::transfer_subnet_ownership(netuid, old_coldkey, &new_coldkey); - Self::transfer_auto_stake_destination(netuid, old_coldkey, &new_coldkey); - Self::transfer_coldkey_stake(netuid, old_coldkey, &new_coldkey); + Self::transfer_subnet_ownership(netuid, old_coldkey, new_coldkey); + Self::transfer_auto_stake_destination(netuid, old_coldkey, new_coldkey); + Self::transfer_coldkey_stake(netuid, old_coldkey, new_coldkey); } - Self::transfer_staking_hotkeys(old_coldkey, &new_coldkey); - Self::transfer_hotkeys_ownership(old_coldkey, &new_coldkey); + Self::transfer_staking_hotkeys(old_coldkey, new_coldkey); + Self::transfer_hotkeys_ownership(old_coldkey, new_coldkey); // Transfer any remaining balance from old_coldkey to new_coldkey let remaining_balance = Self::get_coldkey_balance(old_coldkey); if remaining_balance > 0 { Self::kill_coldkey_account(old_coldkey, remaining_balance)?; - Self::add_balance_to_coldkey_account(&new_coldkey, remaining_balance); + Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); } - Self::set_last_tx_block(&new_coldkey, Self::get_current_block_as_u64()); + Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 367dda4927..7f12665c59 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -39,14 +39,14 @@ fn test_announce_coldkey_swap_works() { assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), + RuntimeOrigin::signed(who), new_coldkey_hash, )); let now = System::block_number(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who.clone(), (now, new_coldkey_hash))] + vec![(who, (now, new_coldkey_hash))] ); assert_eq!( last_event(), @@ -71,28 +71,28 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), + RuntimeOrigin::signed(who), new_coldkey_hash, )); let now = System::block_number(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who.clone(), (now, new_coldkey_hash))] + vec![(who, (now, new_coldkey_hash))] ); let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), + RuntimeOrigin::signed(who), new_coldkey_2_hash, )); let now = System::block_number(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who.clone(), (now, new_coldkey_2_hash))] + vec![(who, (now, new_coldkey_2_hash))] ); }); } @@ -127,24 +127,21 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), + RuntimeOrigin::signed(who), new_coldkey_hash, )); let now = System::block_number(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who.clone(), (now, new_coldkey_hash))] + vec![(who, (now, new_coldkey_hash))] ); let unmet_delay = ColdkeySwapAnnouncementDelay::::get(); System::run_to_block::(now + unmet_delay); assert_noop!( - SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), - new_coldkey_2_hash, - ), + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_2_hash,), Error::::ColdKeySwapReannouncedTooEarly ); }); @@ -174,12 +171,12 @@ fn test_swap_coldkey_announced_works() { // Announce the coldkey swap assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), + RuntimeOrigin::signed(who), new_coldkey_hash, )); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who.clone(), (now, new_coldkey_hash))] + vec![(who, (now, new_coldkey_hash))] ); // Run some blocks for the announcement to be past the delay @@ -222,8 +219,8 @@ fn test_swap_coldkey_announced_works() { register_ok_neuron(netuid1, hotkey3, who, 0); let hotkeys = vec![hotkey1, hotkey2, hotkey3]; - assert_eq!(StakingHotkeys::::get(&who), hotkeys); - assert_eq!(OwnedHotkeys::::get(&who), hotkeys); + assert_eq!(StakingHotkeys::::get(who), hotkeys); + assert_eq!(OwnedHotkeys::::get(who), hotkeys); assert_eq!(Owner::::get(hotkey1), who); assert_eq!(Owner::::get(hotkey2), who); assert_eq!(Owner::::get(hotkey3), who); @@ -279,8 +276,8 @@ fn test_swap_coldkey_announced_works() { ); // Ensure the identity is correctly swapped - assert!(IdentitiesV2::::get(&who).is_none()); - assert_eq!(IdentitiesV2::::get(&new_coldkey), Some(identity)); + assert!(IdentitiesV2::::get(who).is_none()); + assert_eq!(IdentitiesV2::::get(new_coldkey), Some(identity)); // Ensure the subnet ownerships are correctly swapped assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); @@ -353,12 +350,12 @@ fn test_swap_coldkey_announced_works() { ); // Ensure the staking hotkeys are correctly swapped - assert!(StakingHotkeys::::get(&who).is_empty()); - assert_eq!(StakingHotkeys::::get(&new_coldkey), hotkeys); + assert!(StakingHotkeys::::get(who).is_empty()); + assert_eq!(StakingHotkeys::::get(new_coldkey), hotkeys); // Ensure the hotkey ownership is correctly swapped - assert!(OwnedHotkeys::::get(&who).is_empty()); - assert_eq!(OwnedHotkeys::::get(&new_coldkey), hotkeys); + assert!(OwnedHotkeys::::get(who).is_empty()); + assert_eq!(OwnedHotkeys::::get(new_coldkey), hotkeys); assert_eq!(Owner::::get(hotkey1), new_coldkey); assert_eq!(Owner::::get(hotkey2), new_coldkey); assert_eq!(Owner::::get(hotkey3), new_coldkey); @@ -458,7 +455,7 @@ fn test_swap_coldkey_announced_with_mismatched_coldkey_hash_fails() { let other_coldkey = U256::from(3); assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), + RuntimeOrigin::signed(who), new_coldkey_hash, )); @@ -477,7 +474,7 @@ fn test_swap_coldkey_announced_too_early_fails() { let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), + RuntimeOrigin::signed(who), new_coldkey_hash, )); @@ -500,7 +497,7 @@ fn test_swap_coldkey_announced_with_already_associated_coldkey_fails() { let hotkey = U256::from(3); assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), + RuntimeOrigin::signed(who), new_coldkey_hash, )); @@ -532,7 +529,7 @@ fn test_swap_coldkey_announced_with_hotkey_fails() { let hotkey_hash = ::Hashing::hash_of(&hotkey); assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who.clone()), + RuntimeOrigin::signed(who), hotkey_hash, )); From 839194540a8b1b5d708d240aefb4253514b49141 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 8 Dec 2025 14:16:23 +0100 Subject: [PATCH 28/62] reinstate swap_coldkey call + add call to remove announcement as root --- pallets/admin-utils/src/lib.rs | 2 +- pallets/subtensor/src/macros/dispatches.rs | 44 +++++-- pallets/subtensor/src/macros/errors.rs | 6 +- pallets/subtensor/src/swap/swap_coldkey.rs | 2 +- pallets/subtensor/src/tests/claim_root.rs | 8 +- pallets/subtensor/src/tests/swap_coldkey.rs | 135 ++++++++++++-------- 6 files changed, 123 insertions(+), 74 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index de1938f7ee..2b4fc35960 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -2199,7 +2199,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; pallet_subtensor::Pallet::::set_coldkey_swap_announcement_delay(duration); - log::trace!("ColdkeySwapScheduleDurationSet( duration: {duration:?} )"); + log::trace!("ColdkeySwapAnnouncementDelaySet( duration: {duration:?} )"); Ok(()) } } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index e4a4b6f106..6d1382596a 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1065,19 +1065,24 @@ mod dispatches { Self::do_swap_hotkey(origin, &hotkey, &new_hotkey, netuid) } - /// The extrinsic for user to change the coldkey associated with their account. + /// Performs an arbitrary coldkey swap for any coldkey. /// - /// WARNING: This is deprecated in favor of `announce_coldkey_swap`/`coldkey_swap` + /// Only callable by root as it doesn't require an announcement and can be used to swap any coldkey. #[pallet::call_index(71)] #[pallet::weight(Weight::zero())] - #[deprecated(note = "Deprecated, please migrate to `announce_coldkey_swap`/`coldkey_swap`")] pub fn swap_coldkey( - _origin: OriginFor, - _old_coldkey: T::AccountId, - _new_coldkey: T::AccountId, - _swap_cost: TaoCurrency, + origin: OriginFor, + old_coldkey: T::AccountId, + new_coldkey: T::AccountId, + swap_cost: TaoCurrency, ) -> DispatchResult { - Err(Error::::Deprecated.into()) + ensure_root(origin)?; + + Self::do_swap_coldkey(&old_coldkey, &new_coldkey, swap_cost)?; + // We also remove any announcement for security reasons + ColdkeySwapAnnouncements::::remove(old_coldkey); + + Ok(()) } /// Sets the childkey take for a given hotkey. @@ -2351,7 +2356,7 @@ mod dispatches { let when = existing.0; ensure!( now > when.saturating_add(delay), - Error::::ColdKeySwapReannouncedTooEarly + Error::::ColdkeySwapReannouncedTooEarly ); } @@ -2376,7 +2381,7 @@ mod dispatches { let who = ensure_signed(origin)?; let (when, new_coldkey_hash) = ColdkeySwapAnnouncements::::take(who.clone()) - .ok_or(Error::::ColdKeySwapAnnouncementNotFound)?; + .ok_or(Error::::ColdkeySwapAnnouncementNotFound)?; ensure!( new_coldkey_hash == T::Hashing::hash_of(&new_coldkey), @@ -2387,12 +2392,27 @@ mod dispatches { let delay = ColdkeySwapAnnouncementDelay::::get(); ensure!( now > when.saturating_add(delay), - Error::::ColdKeySwapTooEarly + Error::::ColdkeySwapTooEarly ); - Self::do_swap_coldkey(&who, &new_coldkey)?; + let swap_cost = Self::get_key_swap_cost(); + Self::do_swap_coldkey(&who, &new_coldkey, swap_cost)?; Ok(()) } + + /// Removes a coldkey swap announcement for a coldkey. + /// + /// Only callable by root. + #[pallet::call_index(127)] + #[pallet::weight(Weight::zero())] + pub fn remove_coldkey_swap_announcement( + origin: OriginFor, + coldkey: T::AccountId, + ) -> DispatchResult { + ensure_root(origin)?; + ColdkeySwapAnnouncements::::remove(coldkey); + Ok(()) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 710d9c4487..174d9c547f 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -149,11 +149,11 @@ mod errors { /// Default transaction rate limit exceeded. TxRateLimitExceeded, /// Coldkey swap announcement not found - ColdKeySwapAnnouncementNotFound, + ColdkeySwapAnnouncementNotFound, /// Coldkey swap too early. - ColdKeySwapTooEarly, + ColdkeySwapTooEarly, /// Coldkey swap reannounced too early. - ColdKeySwapReannouncedTooEarly, + ColdkeySwapReannouncedTooEarly, /// The announced coldkey hash does not match the new coldkey hash. AnnouncedColdkeyHashDoesNotMatch, /// New coldkey is hotkey diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 277f912192..51aec6798e 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -7,6 +7,7 @@ impl Pallet { pub fn do_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, + swap_cost: TaoCurrency, ) -> DispatchResult { ensure!( StakingHotkeys::::get(new_coldkey).is_empty(), @@ -18,7 +19,6 @@ impl Pallet { ); // Remove and recycle the swap cost from the old coldkey's account - let swap_cost = Self::get_key_swap_cost(); ensure!( Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost.into()), Error::::NotEnoughBalanceToPaySwapColdKey diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 0158579553..2fbfa309e7 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1184,9 +1184,11 @@ fn test_claim_root_with_swap_coldkey() { ); // Swap coldkey - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost.to_u64()); - assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &coldkey, + &new_coldkey, + TaoCurrency::ZERO + )); // Check swapped keys claimed values diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 7f12665c59..9c48466ff9 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -142,7 +142,7 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() assert_noop!( SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_2_hash,), - Error::::ColdKeySwapReannouncedTooEarly + Error::::ColdkeySwapReannouncedTooEarly ); }); } @@ -266,7 +266,7 @@ fn test_swap_coldkey_announced_works() { )); // Ensure the announcement has been consumed - assert_eq!(ColdkeySwapAnnouncements::::get(who), None); + assert_eq!(!ColdkeySwapAnnouncements::::contains_key(who)); // Ensure the cost has been withdrawn from the old coldkey and recycled let balance_after = SubtensorModule::get_coldkey_balance(&who); @@ -404,10 +404,11 @@ fn test_do_swap_coldkey_preserves_new_coldkey_identity() { }; IdentitiesV2::::insert(new_coldkey, new_identity.clone()); - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&who, &new_coldkey)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &who, + &new_coldkey, + TaoCurrency::ZERO + )); // Identity is preserved assert_eq!(IdentitiesV2::::get(who), Some(old_identity)); @@ -441,7 +442,7 @@ fn test_swap_coldkey_announced_without_announcement_fails() { assert_noop!( SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who), new_coldkey), - Error::::ColdKeySwapAnnouncementNotFound + Error::::ColdkeySwapAnnouncementNotFound ); }) } @@ -483,7 +484,7 @@ fn test_swap_coldkey_announced_too_early_fails() { ::RuntimeOrigin::signed(who), new_coldkey ), - Error::::ColdKeySwapTooEarly + Error::::ColdkeySwapTooEarly ); }) } @@ -562,8 +563,9 @@ fn test_do_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); + let swap_cost = SubtensorModule::get_key_swap_cost(); assert_noop!( - SubtensorModule::do_swap_coldkey(&who, &new_coldkey), + SubtensorModule::do_swap_coldkey(&who, &new_coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapColdKey ); }); @@ -575,10 +577,11 @@ fn test_do_swap_coldkey_with_no_stake() { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + TaoCurrency::ZERO + )); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), @@ -648,14 +651,15 @@ fn test_do_swap_coldkey_with_max_values() { netuid2, ); - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey2, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + TaoCurrency::ZERO + )); assert_ok!(SubtensorModule::do_swap_coldkey( &old_coldkey2, - &new_coldkey2 + &new_coldkey2, + TaoCurrency::ZERO )); assert_eq!( @@ -713,10 +717,11 @@ fn test_do_swap_coldkey_effect_on_delegated_stake() { let coldkey_stake_before = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); let delegator_stake_before = SubtensorModule::get_total_stake_for_coldkey(&delegator); - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + TaoCurrency::ZERO + )); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), @@ -808,11 +813,12 @@ fn test_swap_delegated_stake_for_coldkey() { let total_hotkey1_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey1); let total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + TaoCurrency::ZERO + )); // Verify stake transfer assert_eq!( @@ -1100,16 +1106,17 @@ fn test_coldkey_swap_total() { vec![hotkey3, delegate3] ); - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost.to_u64()); - // Perform the swap let new_coldkey = U256::from(1100); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&coldkey), ck_stake ); - assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &coldkey, + &new_coldkey, + TaoCurrency::ZERO + )); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), ck_stake @@ -1243,11 +1250,12 @@ fn test_do_swap_coldkey_effect_on_delegations() { stake.into() )); - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost.to_u64()); - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &coldkey, + &new_coldkey, + TaoCurrency::ZERO + )); // Verify stake was moved for the delegate let approx_total_stake = TaoCurrency::from(stake * 2 - fee * 2); @@ -1283,6 +1291,44 @@ fn test_do_swap_coldkey_effect_on_delegations() { }); } +#[test] +fn test_remove_coldkey_swap_announcement_works() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who), + new_coldkey_hash, + )); + + assert_ok!(SubtensorModule::remove_coldkey_swap_announcement( + RuntimeOrigin::root(), + who, + )); + + assert!(!ColdkeySwapAnnouncements::::contains_key(who)); + }); +} + +#[test] +fn test_remove_coldkey_swap_announcement_with_bad_origin_fails() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + + assert_noop!( + SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::none(), who), + BadOrigin + ); + + assert_noop!( + SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::root(), who), + BadOrigin + ); + }); +} + #[test] fn test_subtensor_extension_rejects_any_call_that_is_not_swap_coldkey_announced() { new_test_ext(0).execute_with(|| { @@ -1401,25 +1447,6 @@ fn test_subtensor_extension_rejects_any_call_that_is_not_swap_coldkey_announced( }); } -#[test] -#[allow(deprecated)] -fn test_swap_coldkey_deprecated() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - assert_noop!( - SubtensorModule::swap_coldkey( - <::RuntimeOrigin>::root(), - old_coldkey, - new_coldkey, - TaoCurrency::MAX - ), - Error::::Deprecated - ); - }); -} - #[test] #[allow(deprecated)] fn test_schedule_swap_coldkey_deprecated() { From 2e6e1cb81d178e66f3855dc8083c8c5bbc40acb3 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 8 Dec 2025 15:24:17 +0100 Subject: [PATCH 29/62] rework test + add tests for swap_coldkey as root --- pallets/subtensor/src/tests/swap_coldkey.rs | 652 +++++++++++++------- 1 file changed, 421 insertions(+), 231 deletions(-) diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 9c48466ff9..666e4a69fe 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -164,258 +164,67 @@ fn test_swap_coldkey_announced_works() { let stake3 = min_stake * 30; let now = System::block_number(); - SubtensorModule::add_balance_to_coldkey_account( - &who, - stake1 + stake2 + stake3 + swap_cost.to_u64() + left_over, - ); - - // Announce the coldkey swap assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who), new_coldkey_hash, )); - assert_eq!( - ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who, (now, new_coldkey_hash))] - ); // Run some blocks for the announcement to be past the delay - // WARN: this is required before staking to neurons to avoid - // value mismatch due to coinbase run let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); - // Setup networks and subnet ownerships - let netuid1 = NetUid::from(1); - let netuid2 = NetUid::from(2); - add_network(netuid1, 1, 0); - add_network(netuid2, 1, 0); - SubnetOwner::::insert(netuid1, who); - SubnetOwner::::insert(netuid2, who); - - // Setup reserves - let reserve1 = (stake1 + stake3) * 10; - let reserve2 = stake2 * 10; - mock::setup_reserves(netuid1, reserve1.into(), reserve1.into()); - mock::setup_reserves(netuid2, reserve2.into(), reserve2.into()); - - // Setup auto stake destinations - AutoStakeDestination::::insert(who, netuid1, hotkey1); - AutoStakeDestination::::insert(who, netuid2, hotkey2); - AutoStakeDestinationColdkeys::::insert( - hotkey1, + let ( netuid1, - vec![who, U256::from(3), U256::from(4)], - ); - AutoStakeDestinationColdkeys::::insert( - hotkey2, netuid2, - vec![U256::from(7), U256::from(8), who], - ); - - // Setup neurons with stake - register_ok_neuron(netuid1, hotkey1, who, 0); - register_ok_neuron(netuid2, hotkey2, who, 0); - register_ok_neuron(netuid1, hotkey3, who, 0); - - let hotkeys = vec![hotkey1, hotkey2, hotkey3]; - assert_eq!(StakingHotkeys::::get(who), hotkeys); - assert_eq!(OwnedHotkeys::::get(who), hotkeys); - assert_eq!(Owner::::get(hotkey1), who); - assert_eq!(Owner::::get(hotkey2), who); - assert_eq!(Owner::::get(hotkey3), who); - - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(who), + hotkeys, + hk1_alpha, + hk2_alpha, + hk3_alpha, + total_ck_stake, + identity, + balance_before, + total_stake_before, + ) = comprehensive_setup!( + who, + new_coldkey, + new_coldkey_hash, + swap_cost, + left_over, + stake1, + stake2, + stake3, hotkey1, - netuid1, - stake1.into() - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(who), hotkey2, - netuid2, - stake2.into() - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(who), - hotkey3, - netuid1, - stake3.into() - )); - let hk1_alpha = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &who, netuid1); - let hk2_alpha = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey2, &who, netuid2); - let hk3_alpha = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey3, &who, netuid1); - let total_ck_stake = SubtensorModule::get_total_stake_for_coldkey(&who); - - // Setup identity - let identity = ChainIdentityV2::default(); - IdentitiesV2::::insert(who, identity.clone()); - assert_eq!(IdentitiesV2::::get(who), Some(identity.clone())); - assert!(IdentitiesV2::::get(new_coldkey).is_none()); - - let balance_before = SubtensorModule::get_coldkey_balance(&who); - let total_stake_before = SubtensorModule::get_total_stake(); + hotkey3 + ); assert_ok!(SubtensorModule::swap_coldkey_announced( ::RuntimeOrigin::signed(who), new_coldkey )); - // Ensure the announcement has been consumed - assert_eq!(!ColdkeySwapAnnouncements::::contains_key(who)); - - // Ensure the cost has been withdrawn from the old coldkey and recycled - let balance_after = SubtensorModule::get_coldkey_balance(&who); - assert_eq!( - balance_before - swap_cost.to_u64(), - balance_after + left_over - ); - - // Ensure the identity is correctly swapped - assert!(IdentitiesV2::::get(who).is_none()); - assert_eq!(IdentitiesV2::::get(new_coldkey), Some(identity)); - - // Ensure the subnet ownerships are correctly swapped - assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); - assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); - - // Ensure the auto stake destinations are correctly swapped - assert!(AutoStakeDestination::::get(who, netuid1).is_none()); - assert!(AutoStakeDestination::::get(who, netuid2).is_none()); - assert_eq!( - AutoStakeDestination::::get(new_coldkey, netuid1), - Some(hotkey1) - ); - assert_eq!( - AutoStakeDestination::::get(new_coldkey, netuid2), - Some(hotkey2) - ); - assert_eq!( - AutoStakeDestinationColdkeys::::get(hotkey1, netuid1), - vec![U256::from(3), U256::from(4), new_coldkey] - ); - assert_eq!( - AutoStakeDestinationColdkeys::::get(hotkey2, netuid2), - vec![U256::from(7), U256::from(8), new_coldkey] - ); - - // Ensure the coldkey stake is correctly swapped - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &who, netuid1), - AlphaCurrency::ZERO - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey2, &who, netuid2), - AlphaCurrency::ZERO - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey3, &who, netuid1), - AlphaCurrency::ZERO - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &new_coldkey, - netuid1 - ), - hk1_alpha - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &new_coldkey, - netuid2 - ), - hk2_alpha - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey3, - &new_coldkey, - netuid1 - ), - hk3_alpha - ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&who), - TaoCurrency::ZERO - ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + comprehensive_checks!( + who, + hotkey1, + hotkey2, + hotkey3, + hotkeys, + new_coldkey, + balance_before, + left_over, + swap_cost, + identity, + netuid1, + netuid2, + hk1_alpha, + hk2_alpha, + hk3_alpha, total_ck_stake, - ); - - // Ensure the staking hotkeys are correctly swapped - assert!(StakingHotkeys::::get(who).is_empty()); - assert_eq!(StakingHotkeys::::get(new_coldkey), hotkeys); - - // Ensure the hotkey ownership is correctly swapped - assert!(OwnedHotkeys::::get(who).is_empty()); - assert_eq!(OwnedHotkeys::::get(new_coldkey), hotkeys); - assert_eq!(Owner::::get(hotkey1), new_coldkey); - assert_eq!(Owner::::get(hotkey2), new_coldkey); - assert_eq!(Owner::::get(hotkey3), new_coldkey); - - // Ensure the remaining balance is transferred to the new coldkey - assert_eq!(SubtensorModule::get_coldkey_balance(&who), 0); - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey), - left_over - ); - - // Ensure total stake is unchanged - assert_eq!( - SubtensorModule::get_total_stake(), - total_stake_before, - "Total stake changed unexpectedly" - ); - - // Verify event emission - System::assert_last_event( - Event::ColdkeySwapped { - old_coldkey: who, - new_coldkey, - swap_cost, - } - .into(), + total_stake_before ); }); } -#[test] -fn test_do_swap_coldkey_preserves_new_coldkey_identity() { - new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let new_coldkey = U256::from(2); - - let old_identity = ChainIdentityV2 { - name: b"Old identity".to_vec(), - ..Default::default() - }; - IdentitiesV2::::insert(who, old_identity.clone()); - - let new_identity = ChainIdentityV2 { - name: b"New identity".to_vec(), - ..Default::default() - }; - IdentitiesV2::::insert(new_coldkey, new_identity.clone()); - - assert_ok!(SubtensorModule::do_swap_coldkey( - &who, - &new_coldkey, - TaoCurrency::ZERO - )); - - // Identity is preserved - assert_eq!(IdentitiesV2::::get(who), Some(old_identity)); - assert_eq!(IdentitiesV2::::get(new_coldkey), Some(new_identity)); - }); -} - #[test] fn test_swap_coldkey_announced_with_bad_origin_fails() { new_test_ext(1).execute_with(|| { @@ -553,6 +362,136 @@ fn test_swap_coldkey_announced_with_hotkey_fails() { }) } +#[test] +fn test_swap_coldkey_works() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let hotkey1 = U256::from(1001); + let hotkey2 = U256::from(1002); + let hotkey3 = U256::from(1003); + let swap_cost = SubtensorModule::get_key_swap_cost(); + let left_over = 12345; + let min_stake = DefaultMinStake::::get().to_u64(); + let stake1 = min_stake * 10; + let stake2 = min_stake * 20; + let stake3 = min_stake * 30; + + let ( + netuid1, + netuid2, + hotkeys, + hk1_alpha, + hk2_alpha, + hk3_alpha, + total_ck_stake, + identity, + balance_before, + total_stake_before, + ) = comprehensive_setup!( + old_coldkey, + new_coldkey, + new_coldkey_hash, + swap_cost, + left_over, + stake1, + stake2, + stake3, + hotkey1, + hotkey2, + hotkey3 + ); + + assert_ok!(SubtensorModule::swap_coldkey( + ::RuntimeOrigin::root(), + old_coldkey, + new_coldkey, + swap_cost, + )); + + comprehensive_checks!( + old_coldkey, + hotkey1, + hotkey2, + hotkey3, + hotkeys, + new_coldkey, + balance_before, + left_over, + swap_cost, + identity, + netuid1, + netuid2, + hk1_alpha, + hk2_alpha, + hk3_alpha, + total_ck_stake, + total_stake_before + ); + }); +} + +#[test] +fn test_swap_coldkey_with_bad_origin_fails() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let old_coldkey = U256::from(2); + let new_coldkey = U256::from(3); + let swap_cost = SubtensorModule::get_key_swap_cost(); + + assert_noop!( + SubtensorModule::swap_coldkey( + ::RuntimeOrigin::signed(who), + old_coldkey, + new_coldkey, + swap_cost, + ), + BadOrigin + ); + + assert_noop!( + SubtensorModule::swap_coldkey( + ::RuntimeOrigin::none(), + old_coldkey, + new_coldkey, + swap_cost + ), + BadOrigin + ); + }); +} + +#[test] +fn test_do_swap_coldkey_preserves_new_coldkey_identity() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + + let old_identity = ChainIdentityV2 { + name: b"Old identity".to_vec(), + ..Default::default() + }; + IdentitiesV2::::insert(who, old_identity.clone()); + + let new_identity = ChainIdentityV2 { + name: b"New identity".to_vec(), + ..Default::default() + }; + IdentitiesV2::::insert(new_coldkey, new_identity.clone()); + + assert_ok!(SubtensorModule::do_swap_coldkey( + &who, + &new_coldkey, + TaoCurrency::ZERO + )); + + // Identity is preserved + assert_eq!(IdentitiesV2::::get(who), Some(old_identity)); + assert_eq!(IdentitiesV2::::get(new_coldkey), Some(new_identity)); + }); +} + #[test] fn test_do_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { new_test_ext(1).execute_with(|| { @@ -1316,14 +1255,15 @@ fn test_remove_coldkey_swap_announcement_works() { fn test_remove_coldkey_swap_announcement_with_bad_origin_fails() { new_test_ext(1).execute_with(|| { let who = U256::from(1); + let coldkey = U256::from(2); assert_noop!( - SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::none(), who), + SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::signed(who), coldkey), BadOrigin ); assert_noop!( - SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::root(), who), + SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::none(), coldkey), BadOrigin ); }); @@ -1464,4 +1404,254 @@ fn test_schedule_swap_coldkey_deprecated() { }); } -// TEST TRANSFER ROOT CLAIM WITH NEW KEYS + ROOT CASE +#[macro_export] +macro_rules! comprehensive_setup { + ( + $who:expr, + $new_coldkey:expr, + $new_coldkey_hash:expr, + $swap_cost:expr, + $left_over:expr, + $stake1:expr, + $stake2:expr, + $stake3:expr, + $hotkey1:expr, + $hotkey2:expr, + $hotkey3:expr + ) => {{ + SubtensorModule::add_balance_to_coldkey_account( + &$who, + $stake1 + $stake2 + $stake3 + $swap_cost.to_u64() + $left_over, + ); + + // Setup networks and subnet ownerships + let netuid1 = NetUid::from(1); + let netuid2 = NetUid::from(2); + add_network(netuid1, 1, 0); + add_network(netuid2, 1, 0); + SubnetOwner::::insert(netuid1, $who); + SubnetOwner::::insert(netuid2, $who); + + // Setup reserves + let reserve1 = ($stake1 + $stake3) * 10; + let reserve2 = $stake2 * 10; + mock::setup_reserves(netuid1, reserve1.into(), reserve1.into()); + mock::setup_reserves(netuid2, reserve2.into(), reserve2.into()); + + // Setup auto stake destinations + AutoStakeDestination::::insert($who, netuid1, $hotkey1); + AutoStakeDestination::::insert($who, netuid2, $hotkey2); + AutoStakeDestinationColdkeys::::insert( + $hotkey1, + netuid1, + vec![$who, U256::from(3), U256::from(4)], + ); + AutoStakeDestinationColdkeys::::insert( + $hotkey2, + netuid2, + vec![U256::from(7), U256::from(8), $who], + ); + + // Setup neurons with stake + register_ok_neuron(netuid1, $hotkey1, $who, 0); + register_ok_neuron(netuid2, $hotkey2, $who, 0); + register_ok_neuron(netuid1, $hotkey3, $who, 0); + + let hotkeys = vec![$hotkey1, $hotkey2, $hotkey3]; + assert_eq!(StakingHotkeys::::get($who), hotkeys); + assert_eq!(OwnedHotkeys::::get($who), hotkeys); + assert_eq!(Owner::::get($hotkey1), $who); + assert_eq!(Owner::::get($hotkey2), $who); + assert_eq!(Owner::::get($hotkey3), $who); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed($who), + $hotkey1, + netuid1, + $stake1.into() + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed($who), + $hotkey2, + netuid2, + $stake2.into() + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed($who), + $hotkey3, + netuid1, + $stake3.into() + )); + let hk1_alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey1, &$who, netuid1); + let hk2_alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey2, &$who, netuid2); + let hk3_alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey3, &$who, netuid1); + let total_ck_stake = SubtensorModule::get_total_stake_for_coldkey(&$who); + + // Setup identity + let identity = ChainIdentityV2::default(); + IdentitiesV2::::insert($who, identity.clone()); + assert_eq!(IdentitiesV2::::get($who), Some(identity.clone())); + assert!(IdentitiesV2::::get($new_coldkey).is_none()); + + let balance_before = SubtensorModule::get_coldkey_balance(&$who); + let total_stake_before = SubtensorModule::get_total_stake(); + + ( + netuid1, + netuid2, + hotkeys, + hk1_alpha, + hk2_alpha, + hk3_alpha, + total_ck_stake, + identity, + balance_before, + total_stake_before, + ) + }}; +} + +#[macro_export] +macro_rules! comprehensive_checks { + ( + $who:expr, + $hotkey1:expr, + $hotkey2:expr, + $hotkey3:expr, + $hotkeys:expr, + $new_coldkey:expr, + $balance_before:expr, + $left_over:expr, + $swap_cost:expr, + $identity:expr, + $netuid1:expr, + $netuid2:expr, + $hk1_alpha:expr, + $hk2_alpha:expr, + $hk3_alpha:expr, + $total_ck_stake:expr, + $total_stake_before:expr + ) => { + // Ensure the announcement has been consumed + assert!(!ColdkeySwapAnnouncements::::contains_key($who)); + + // Ensure the cost has been withdrawn from the old coldkey and recycled + let balance_after = SubtensorModule::get_coldkey_balance(&$who); + assert_eq!( + $balance_before - $swap_cost.to_u64(), + balance_after + $left_over + ); + + // Ensure the identity is correctly swapped + assert!(IdentitiesV2::::get($who).is_none()); + assert_eq!(IdentitiesV2::::get($new_coldkey), Some($identity)); + + // Ensure the subnet ownerships are correctly swapped + assert_eq!(SubnetOwner::::get($netuid1), $new_coldkey); + assert_eq!(SubnetOwner::::get($netuid2), $new_coldkey); + + // Ensure the auto stake destinations are correctly swapped + assert!(AutoStakeDestination::::get($who, $netuid1).is_none()); + assert!(AutoStakeDestination::::get($who, $netuid2).is_none()); + assert_eq!( + AutoStakeDestination::::get($new_coldkey, $netuid1), + Some($hotkey1) + ); + assert_eq!( + AutoStakeDestination::::get($new_coldkey, $netuid2), + Some($hotkey2) + ); + assert_eq!( + AutoStakeDestinationColdkeys::::get($hotkey1, $netuid1), + vec![U256::from(3), U256::from(4), $new_coldkey] + ); + assert_eq!( + AutoStakeDestinationColdkeys::::get($hotkey2, $netuid2), + vec![U256::from(7), U256::from(8), $new_coldkey] + ); + + // Ensure the coldkey stake is correctly swapped + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey1, &$who, $netuid1), + 0.into(), + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey2, &$who, $netuid2), + 0.into(), + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey3, &$who, $netuid1), + 0.into(), + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &$hotkey1, + &$new_coldkey, + $netuid1 + ), + $hk1_alpha + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &$hotkey2, + &$new_coldkey, + $netuid2 + ), + $hk2_alpha + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &$hotkey3, + &$new_coldkey, + $netuid1 + ), + $hk3_alpha + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&$who), + TaoCurrency::ZERO + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&$new_coldkey), + $total_ck_stake, + ); + + // Ensure the staking hotkeys are correctly swapped + assert!(StakingHotkeys::::get($who).is_empty()); + assert_eq!(StakingHotkeys::::get($new_coldkey), $hotkeys); + + // Ensure the hotkey ownership is correctly swapped + assert!(OwnedHotkeys::::get($who).is_empty()); + assert_eq!(OwnedHotkeys::::get($new_coldkey), $hotkeys); + assert_eq!(Owner::::get($hotkey1), $new_coldkey); + assert_eq!(Owner::::get($hotkey2), $new_coldkey); + assert_eq!(Owner::::get($hotkey3), $new_coldkey); + + // Ensure the remaining balance is transferred to the new coldkey + assert_eq!(SubtensorModule::get_coldkey_balance(&$who), 0); + assert_eq!( + SubtensorModule::get_coldkey_balance(&$new_coldkey), + $left_over + ); + + // Ensure total stake is unchanged + assert_eq!( + SubtensorModule::get_total_stake(), + $total_stake_before, + "Total stake changed unexpectedly" + ); + + // Verify event emission + System::assert_last_event( + Event::ColdkeySwapped { + old_coldkey: $who, + new_coldkey: $new_coldkey, + swap_cost: $swap_cost, + } + .into(), + ); + }; +} From 44837ec389e1b6066cfb37d9a2c530e21581cb19 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 8 Dec 2025 16:39:30 +0100 Subject: [PATCH 30/62] fix clippy --- pallets/subtensor/src/tests/swap_coldkey.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 666e4a69fe..8a4bf2b3bf 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -3,7 +3,8 @@ clippy::expect_used, clippy::indexing_slicing, clippy::panic, - clippy::unwrap_used + clippy::unwrap_used, + clippy::arithmetic_side_effects )] use approx::assert_abs_diff_eq; From 39ef15eba4429fae445236c617a12504375b7544 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Dec 2025 15:09:55 +0100 Subject: [PATCH 31/62] remove useless block number from event --- pallets/subtensor/src/macros/dispatches.rs | 5 ++--- pallets/subtensor/src/macros/events.rs | 2 -- pallets/subtensor/src/tests/swap_coldkey.rs | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 6d1382596a..87acf41289 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2363,9 +2363,8 @@ mod dispatches { ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash.clone())); Self::deposit_event(Event::ColdkeySwapAnnounced { - who: who.clone(), - new_coldkey_hash: new_coldkey_hash.clone(), - block_number: now, + who, + new_coldkey_hash, }); Ok(()) } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 4babd7f0a8..0c2fc49a69 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -176,8 +176,6 @@ mod events { who: T::AccountId, /// The hash of the new coldkey. new_coldkey_hash: T::Hash, - /// The block number the announcement was made. - block_number: BlockNumberFor, }, /// A coldkey swap announcement has been removed. ColdkeySwapAnnouncementRemoved { diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 8a4bf2b3bf..9dc35358c2 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -54,7 +54,6 @@ fn test_announce_coldkey_swap_works() { RuntimeEvent::SubtensorModule(Event::ColdkeySwapAnnounced { who, new_coldkey_hash, - block_number: now, }) ); }); From 4b64484d6ce457a2c86e0c8018b174e4bcacdabe Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Dec 2025 16:04:02 +0100 Subject: [PATCH 32/62] fix benchmarks --- pallets/subtensor/src/benchmarks.rs | 94 +++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 26 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 476be905e9..f6fd0f67ba 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -369,17 +369,6 @@ mod pallet_benchmarks { ); } - #[benchmark] - fn schedule_swap_coldkey() { - let old_coldkey: T::AccountId = account("old_cold", 0, 1); - let new_coldkey: T::AccountId = account("new_cold", 1, 2); - let amount: u64 = 100_000_000_000_000; - Subtensor::::add_balance_to_coldkey_account(&old_coldkey, amount); - - #[extrinsic_call] - _(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone()); - } - #[benchmark] fn sudo_set_tx_childkey_take_rate_limit() { let new_rate_limit: u64 = 100; @@ -419,14 +408,31 @@ mod pallet_benchmarks { } #[benchmark] - fn swap_coldkey() { + fn announce_coldkey_swap() { + let coldkey: T::AccountId = account("old_coldkey", 0, 0); + let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); + let new_coldkey_hash: T::Hash = ::Hashing::hash_of(&new_coldkey); + + #[extrinsic_call] + _(RawOrigin::Signed(coldkey), new_coldkey_hash); + } + + #[benchmark] + fn swap_coldkey_announced() { let old_coldkey: T::AccountId = account("old_coldkey", 0, 0); let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); + let new_coldkey_hash: T::Hash = ::Hashing::hash_of(&new_coldkey); let hotkey1: T::AccountId = account("hotkey1", 0, 0); - let netuid = NetUid::from(1); + + let now = frame_system::Pallet::::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); + ColdkeySwapAnnouncements::::insert(&old_coldkey, (now, new_coldkey_hash.clone())); + frame_system::Pallet::::set_block_number(now + delay); + let swap_cost = Subtensor::::get_key_swap_cost(); - let free_balance_old = swap_cost + 12345.into(); + Subtensor::::add_balance_to_coldkey_account(&old_coldkey, swap_cost.into()); + let netuid = NetUid::from(1); Subtensor::::init_new_network(netuid, 1); Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_network_pow_registration_allowed(netuid, true); @@ -444,18 +450,54 @@ mod pallet_benchmarks { old_coldkey.clone(), ); - Subtensor::::add_balance_to_coldkey_account(&old_coldkey, free_balance_old.into()); - let name: Vec = b"The fourth Coolest Identity".to_vec(); - let identity = ChainIdentityV2 { - name, - url: vec![], - github_repo: vec![], - image: vec![], - discord: vec![], - description: vec![], - additional: vec![], - }; - IdentitiesV2::::insert(&old_coldkey, identity); + #[extrinsic_call] + _(RawOrigin::Signed(old_coldkey), new_coldkey); + } + + #[benchmark] + fn remove_coldkey_swap_announcement() { + let coldkey: T::AccountId = account("old_coldkey", 0, 0); + let coldkey_hash: T::Hash = ::Hashing::hash_of(&coldkey); + let now = frame_system::Pallet::::block_number(); + + ColdkeySwapAnnouncements::::insert(&coldkey, (now, coldkey_hash)); + + #[extrinsic_call] + _(RawOrigin::Root, coldkey); + } + + #[benchmark] + fn swap_coldkey() { + let old_coldkey: T::AccountId = account("old_coldkey", 0, 0); + let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); + let new_coldkey_hash: T::Hash = ::Hashing::hash_of(&new_coldkey); + let hotkey1: T::AccountId = account("hotkey1", 0, 0); + + let now = frame_system::Pallet::::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); + ColdkeySwapAnnouncements::::insert(&old_coldkey, (now, new_coldkey_hash.clone())); + frame_system::Pallet::::set_block_number(now + delay); + + let swap_cost = Subtensor::::get_key_swap_cost(); + Subtensor::::add_balance_to_coldkey_account(&old_coldkey, swap_cost.into()); + + let netuid = NetUid::from(1); + Subtensor::::init_new_network(netuid, 1); + Subtensor::::set_network_registration_allowed(netuid, true); + Subtensor::::set_network_pow_registration_allowed(netuid, true); + + let block_number = Subtensor::::get_current_block_as_u64(); + let (nonce, work) = + Subtensor::::create_work_for_block_number(netuid, block_number, 3, &hotkey1); + let _ = Subtensor::::register( + RawOrigin::Signed(old_coldkey.clone()).into(), + netuid, + block_number, + nonce, + work.clone(), + hotkey1.clone(), + old_coldkey.clone(), + ); #[extrinsic_call] _( From e3545208885b78dbb02aa91870df50d10b5a0a63 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Dec 2025 17:17:51 +0100 Subject: [PATCH 33/62] fix benchmarks --- pallets/subtensor/src/benchmarks.rs | 34 +++++++++++++---------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index f6fd0f67ba..865ba8392c 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -426,7 +426,7 @@ mod pallet_benchmarks { let now = frame_system::Pallet::::block_number(); let delay = ColdkeySwapAnnouncementDelay::::get(); - ColdkeySwapAnnouncements::::insert(&old_coldkey, (now, new_coldkey_hash.clone())); + ColdkeySwapAnnouncements::::insert(&old_coldkey, (now, new_coldkey_hash)); frame_system::Pallet::::set_block_number(now + delay); let swap_cost = Subtensor::::get_key_swap_cost(); @@ -454,30 +454,12 @@ mod pallet_benchmarks { _(RawOrigin::Signed(old_coldkey), new_coldkey); } - #[benchmark] - fn remove_coldkey_swap_announcement() { - let coldkey: T::AccountId = account("old_coldkey", 0, 0); - let coldkey_hash: T::Hash = ::Hashing::hash_of(&coldkey); - let now = frame_system::Pallet::::block_number(); - - ColdkeySwapAnnouncements::::insert(&coldkey, (now, coldkey_hash)); - - #[extrinsic_call] - _(RawOrigin::Root, coldkey); - } - #[benchmark] fn swap_coldkey() { let old_coldkey: T::AccountId = account("old_coldkey", 0, 0); let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); - let new_coldkey_hash: T::Hash = ::Hashing::hash_of(&new_coldkey); let hotkey1: T::AccountId = account("hotkey1", 0, 0); - let now = frame_system::Pallet::::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get(); - ColdkeySwapAnnouncements::::insert(&old_coldkey, (now, new_coldkey_hash.clone())); - frame_system::Pallet::::set_block_number(now + delay); - let swap_cost = Subtensor::::get_key_swap_cost(); Subtensor::::add_balance_to_coldkey_account(&old_coldkey, swap_cost.into()); @@ -507,6 +489,20 @@ mod pallet_benchmarks { swap_cost, ); } + + #[benchmark] + fn remove_coldkey_swap_announcement() { + let coldkey: T::AccountId = account("old_coldkey", 0, 0); + let coldkey_hash: T::Hash = ::Hashing::hash_of(&coldkey); + let now = frame_system::Pallet::::block_number(); + + ColdkeySwapAnnouncements::::insert(&coldkey, (now, coldkey_hash)); + + #[extrinsic_call] + _(RawOrigin::Root, coldkey); + } + + #[benchmark] fn batch_reveal_weights() { From bee751d814ae2f7e5c2b71349619b47e90c1e922 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Dec 2025 17:21:28 +0100 Subject: [PATCH 34/62] cargo fmt --- pallets/subtensor/src/benchmarks.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 865ba8392c..40c68ffe28 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -489,7 +489,7 @@ mod pallet_benchmarks { swap_cost, ); } - + #[benchmark] fn remove_coldkey_swap_announcement() { let coldkey: T::AccountId = account("old_coldkey", 0, 0); @@ -502,8 +502,6 @@ mod pallet_benchmarks { _(RawOrigin::Root, coldkey); } - - #[benchmark] fn batch_reveal_weights() { let tempo: u16 = 0; From 7136bfd52fe2a8b9a73121203fc6e2b8843c61d8 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Dec 2025 19:01:03 +0100 Subject: [PATCH 35/62] fix benchmark --- pallets/subtensor/src/benchmarks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 40c68ffe28..e06eeec435 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -427,7 +427,7 @@ mod pallet_benchmarks { let now = frame_system::Pallet::::block_number(); let delay = ColdkeySwapAnnouncementDelay::::get(); ColdkeySwapAnnouncements::::insert(&old_coldkey, (now, new_coldkey_hash)); - frame_system::Pallet::::set_block_number(now + delay); + frame_system::Pallet::::set_block_number(now + delay + 1); let swap_cost = Subtensor::::get_key_swap_cost(); Subtensor::::add_balance_to_coldkey_account(&old_coldkey, swap_cost.into()); From f920c778cc66c93d4479551106ff87de84ffdc63 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Dec 2025 15:28:09 +0100 Subject: [PATCH 36/62] fix clippy --- pallets/subtensor/src/benchmarks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index e06eeec435..a616211407 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -427,7 +427,7 @@ mod pallet_benchmarks { let now = frame_system::Pallet::::block_number(); let delay = ColdkeySwapAnnouncementDelay::::get(); ColdkeySwapAnnouncements::::insert(&old_coldkey, (now, new_coldkey_hash)); - frame_system::Pallet::::set_block_number(now + delay + 1); + frame_system::Pallet::::set_block_number(now + delay + 1u32.into()); let swap_cost = Subtensor::::get_key_swap_cost(); Subtensor::::add_balance_to_coldkey_account(&old_coldkey, swap_cost.into()); From 4e7449591da8c7b6e5f65d1297ea7b7175fd2628 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Dec 2025 19:03:04 +0100 Subject: [PATCH 37/62] fix benchmarks --- pallets/subtensor/src/macros/dispatches.rs | 38 +++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 87acf41289..408b30bdfe 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -711,7 +711,7 @@ mod dispatches { /// #[pallet::call_index(2)] #[pallet::weight((Weight::from_parts(340_800_000, 0) - .saturating_add(T::DbWeight::get().reads(27_u64)) + .saturating_add(T::DbWeight::get().reads(25_u64)) .saturating_add(T::DbWeight::get().writes(16_u64)), DispatchClass::Normal, Pays::Yes))] pub fn add_stake( origin: OriginFor, @@ -1069,7 +1069,11 @@ mod dispatches { /// /// Only callable by root as it doesn't require an announcement and can be used to swap any coldkey. #[pallet::call_index(71)] - #[pallet::weight(Weight::zero())] + #[pallet::weight( + Weight::from_parts(183_600_000, 0) + .saturating_add(T::DbWeight::get().reads(17_u64)) + .saturating_add(T::DbWeight::get().writes(9_u64)) + )] pub fn swap_coldkey( origin: OriginFor, old_coldkey: T::AccountId, @@ -1495,7 +1499,7 @@ mod dispatches { /// - Thrown if key has hit transaction rate limit #[pallet::call_index(84)] #[pallet::weight((Weight::from_parts(358_500_000, 0) - .saturating_add(T::DbWeight::get().reads(44_u64)) + .saturating_add(T::DbWeight::get().reads(41_u64)) .saturating_add(T::DbWeight::get().writes(26_u64)), DispatchClass::Normal, Pays::Yes))] pub fn unstake_all_alpha(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_unstake_all_alpha(origin, hotkey) @@ -1609,7 +1613,7 @@ mod dispatches { #[pallet::call_index(87)] #[pallet::weight(( Weight::from_parts(351_300_000, 0) - .saturating_add(T::DbWeight::get().reads(40_u64)) + .saturating_add(T::DbWeight::get().reads(37_u64)) .saturating_add(T::DbWeight::get().writes(24_u64)), DispatchClass::Normal, Pays::Yes @@ -1674,7 +1678,7 @@ mod dispatches { /// #[pallet::call_index(88)] #[pallet::weight((Weight::from_parts(402_900_000, 0) - .saturating_add(T::DbWeight::get().reads(27_u64)) + .saturating_add(T::DbWeight::get().reads(25_u64)) .saturating_add(T::DbWeight::get().writes(16_u64)), DispatchClass::Normal, Pays::Yes))] pub fn add_stake_limit( origin: OriginFor, @@ -1738,7 +1742,7 @@ mod dispatches { /// #[pallet::call_index(89)] #[pallet::weight((Weight::from_parts(377_400_000, 0) - .saturating_add(T::DbWeight::get().reads(31_u64)) + .saturating_add(T::DbWeight::get().reads(29_u64)) .saturating_add(T::DbWeight::get().writes(15_u64)), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake_limit( origin: OriginFor, @@ -1782,7 +1786,7 @@ mod dispatches { #[pallet::call_index(90)] #[pallet::weight(( Weight::from_parts(411_500_000, 0) - .saturating_add(T::DbWeight::get().reads(40_u64)) + .saturating_add(T::DbWeight::get().reads(37_u64)) .saturating_add(T::DbWeight::get().writes(24_u64)), DispatchClass::Normal, Pays::Yes @@ -1960,7 +1964,7 @@ mod dispatches { /// Without limit_price it remove all the stake similar to `remove_stake` extrinsic #[pallet::call_index(103)] #[pallet::weight((Weight::from_parts(395_300_000, 10142) - .saturating_add(T::DbWeight::get().reads(31_u64)) + .saturating_add(T::DbWeight::get().reads(29_u64)) .saturating_add(T::DbWeight::get().writes(15_u64)), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake_full_limit( origin: T::RuntimeOrigin, @@ -2343,7 +2347,11 @@ mod dispatches { /// Announces a coldkey swap using coldkey hash. /// This is required before the coldkey swap can be performed after the delay period. #[pallet::call_index(125)] - #[pallet::weight(Weight::zero())] + #[pallet::weight( + Weight::from_parts(16_150_000, 0) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + )] pub fn announce_coldkey_swap( origin: OriginFor, new_coldkey_hash: T::Hash, @@ -2372,7 +2380,11 @@ mod dispatches { /// Performs a coldkey swap iff an announcement has been made. /// The provided new coldkey must match the announced coldkey hash. #[pallet::call_index(126)] - #[pallet::weight(Weight::zero())] + #[pallet::weight( + Weight::from_parts(207_300_000, 0) + .saturating_add(T::DbWeight::get().reads(19_u64)) + .saturating_add(T::DbWeight::get().writes(9_u64)) + )] pub fn swap_coldkey_announced( origin: OriginFor, new_coldkey: T::AccountId, @@ -2404,7 +2416,11 @@ mod dispatches { /// /// Only callable by root. #[pallet::call_index(127)] - #[pallet::weight(Weight::zero())] + #[pallet::weight( + Weight::from_parts(4_609_000, 0) + .saturating_add(T::DbWeight::get().reads(0_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + )] pub fn remove_coldkey_swap_announcement( origin: OriginFor, coldkey: T::AccountId, From f0b851ae364448dc09f469ffb398d4c3e279158e Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Dec 2025 19:13:48 +0100 Subject: [PATCH 38/62] bump spec 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 17b626263c..0ac0fb0787 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -237,7 +237,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: 361, + spec_version: 362, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From baae06b4c1ec92f9ccceb0012d5c997f8677bffc Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 11 Dec 2025 16:24:57 +0100 Subject: [PATCH 39/62] fix weights --- pallets/admin-utils/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 2b4fc35960..6321f2363f 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1580,7 +1580,7 @@ pub mod pallet { /// Weight is handled by the `#[pallet::weight]` attribute. #[pallet::call_index(62)] #[pallet::weight(( - Weight::from_parts(10_020_000, 3507) + Weight::from_parts(5_698_000, 0) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(0_u64)), DispatchClass::Operational, From 5a6c0b938ea74fcc0987d7c90fb7a8396a8e9f5a Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 11 Dec 2025 20:21:36 +0100 Subject: [PATCH 40/62] pays swap cost on first announcement --- pallets/subtensor/src/macros/dispatches.rs | 43 ++++- pallets/subtensor/src/macros/events.rs | 8 +- pallets/subtensor/src/swap/swap_coldkey.rs | 23 +-- pallets/subtensor/src/tests/claim_root.rs | 6 +- pallets/subtensor/src/tests/swap_coldkey.rs | 164 ++++++++++---------- 5 files changed, 131 insertions(+), 113 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 408b30bdfe..7368092e2a 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1082,7 +1082,11 @@ mod dispatches { ) -> DispatchResult { ensure_root(origin)?; - Self::do_swap_coldkey(&old_coldkey, &new_coldkey, swap_cost)?; + if swap_cost.to_u64() > 0 { + Self::charge_swap_cost(&old_coldkey, swap_cost)?; + } + Self::do_swap_coldkey(&old_coldkey, &new_coldkey)?; + // We also remove any announcement for security reasons ColdkeySwapAnnouncements::::remove(old_coldkey); @@ -2344,8 +2348,20 @@ mod dispatches { Ok(()) } - /// Announces a coldkey swap using coldkey hash. - /// This is required before the coldkey swap can be performed after the delay period. + /// Announces a coldkey swap using BlakeTwo256 hash of the new coldkey. + /// + /// This is required before the coldkey swap can be performed + /// after the delay period. + /// + /// It can be reannounced after a delay of `ColdkeySwapReannouncementDelay` between the + /// original announcement and the reannouncement. + /// + /// The dispatch origin of this call must be the original coldkey that made the announcement. + /// + /// - `new_coldkey_hash`: The hash of the new coldkey using BlakeTwo256. + /// + /// The `ColdkeySwapAnnounced` event is emitted on successful announcement. + /// #[pallet::call_index(125)] #[pallet::weight( Weight::from_parts(16_150_000, 0) @@ -2366,6 +2382,9 @@ mod dispatches { now > when.saturating_add(delay), Error::::ColdkeySwapReannouncedTooEarly ); + } else { + let swap_cost = Self::get_key_swap_cost(); + Self::charge_swap_cost(&who, swap_cost)?; } ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash.clone())); @@ -2377,8 +2396,14 @@ mod dispatches { Ok(()) } - /// Performs a coldkey swap iff an announcement has been made. - /// The provided new coldkey must match the announced coldkey hash. + /// Performs a coldkey swap if an announcement has been made. + /// + /// The dispatch origin of this call must be the original coldkey that made the announcement. + /// + /// - `new_coldkey`: The new coldkey to swap to. The BlakeTwo256 hash of the new coldkey must be + /// the same as the announced coldkey hash. + /// + /// The `ColdkeySwapped` event is emitted on successful swap. #[pallet::call_index(126)] #[pallet::weight( Weight::from_parts(207_300_000, 0) @@ -2406,15 +2431,17 @@ mod dispatches { Error::::ColdkeySwapTooEarly ); - let swap_cost = Self::get_key_swap_cost(); - Self::do_swap_coldkey(&who, &new_coldkey, swap_cost)?; + Self::do_swap_coldkey(&who, &new_coldkey)?; Ok(()) } /// Removes a coldkey swap announcement for a coldkey. /// - /// Only callable by root. + /// The dispatch origin of this call must be root. + /// + /// - `coldkey`: The coldkey to remove the swap announcement for. + /// #[pallet::call_index(127)] #[pallet::weight( Weight::from_parts(4_609_000, 0) diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 0c2fc49a69..793b30b217 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -182,14 +182,12 @@ mod events { /// The account ID of the coldkey that made the announcement. who: T::AccountId, }, - /// A coldkey has been swapped + /// A coldkey has been swapped. ColdkeySwapped { - /// the account ID of old coldkey + /// The account ID of old coldkey. old_coldkey: T::AccountId, - /// the account ID of new coldkey + /// The account ID of new coldkey. new_coldkey: T::AccountId, - /// the swap cost - swap_cost: TaoCurrency, }, /// All balance of a hotkey has been unstaked and transferred to a new coldkey AllBalanceUnstakedAndTransferredToNewColdkey { diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 51aec6798e..c7119fc066 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -7,7 +7,6 @@ impl Pallet { pub fn do_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, - swap_cost: TaoCurrency, ) -> DispatchResult { ensure!( StakingHotkeys::::get(new_coldkey).is_empty(), @@ -18,14 +17,6 @@ impl Pallet { Error::::NewColdKeyIsHotkey ); - // Remove and recycle the swap cost from the old coldkey's account - ensure!( - Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost.into()), - Error::::NotEnoughBalanceToPaySwapColdKey - ); - let burn_amount = Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost.into())?; - Self::recycle_tao(burn_amount); - // Swap the identity if the old coldkey has one and the new coldkey doesn't if IdentitiesV2::::get(new_coldkey).is_none() && let Some(identity) = IdentitiesV2::::take(old_coldkey) @@ -53,11 +44,23 @@ impl Pallet { Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), - swap_cost, }); Ok(()) } + /// Charges the swap cost from the coldkey's account and recycles the tokens. + pub fn charge_swap_cost(coldkey: &T::AccountId, swap_cost: TaoCurrency) -> DispatchResult { + ensure!( + Self::can_remove_balance_from_coldkey_account(coldkey, swap_cost.into()), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + + let burn_amount = Self::remove_balance_from_coldkey_account(coldkey, swap_cost.into())?; + Self::recycle_tao(burn_amount); + + Ok(()) + } + /// Transfer the ownership of the subnet to the new coldkey if it is owned by the old coldkey. fn transfer_subnet_ownership( netuid: NetUid, diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 2fbfa309e7..a9577157e4 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1184,11 +1184,7 @@ fn test_claim_root_with_swap_coldkey() { ); // Swap coldkey - assert_ok!(SubtensorModule::do_swap_coldkey( - &coldkey, - &new_coldkey, - TaoCurrency::ZERO - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); // Check swapped keys claimed values diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 9dc35358c2..6f003a8d0e 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -36,9 +36,17 @@ fn test_announce_coldkey_swap_works() { let who = U256::from(1); let new_coldkey = U256::from(2); let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let left_over = 12345; assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); + let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost + left_over); + assert_eq!( + SubtensorModule::get_coldkey_balance(&who), + swap_cost + left_over + ); + assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who), new_coldkey_hash, @@ -49,6 +57,7 @@ fn test_announce_coldkey_swap_works() { ColdkeySwapAnnouncements::::iter().collect::>(), vec![(who, (now, new_coldkey_hash))] ); + assert_eq!(SubtensorModule::get_coldkey_balance(&who), left_over); assert_eq!( last_event(), RuntimeEvent::SubtensorModule(Event::ColdkeySwapAnnounced { @@ -70,6 +79,9 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); + let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); + SubtensorModule::add_balance_to_coldkey_account(&who, 2 * swap_cost); + assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who), new_coldkey_hash, @@ -97,6 +109,38 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { }); } +#[test] +fn test_announce_coldkey_swap_only_pays_swap_cost_if_no_announcement_exists() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let new_coldkey_2 = U256::from(3); + let new_coldkey_2_hash = ::Hashing::hash_of(&new_coldkey_2); + let left_over = 12345; + + let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost + left_over); + assert_eq!(SubtensorModule::get_coldkey_balance(&who), swap_cost + left_over); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who), + new_coldkey_hash, + )); + assert_eq!(SubtensorModule::get_coldkey_balance(&who), left_over); + + let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get() + 1; + System::run_to_block::(now + delay); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who), + new_coldkey_2_hash, + )); + assert_eq!(SubtensorModule::get_coldkey_balance(&who), left_over); + }); +} + #[test] fn test_announce_coldkey_swap_with_bad_origin_fails() { new_test_ext(1).execute_with(|| { @@ -126,6 +170,9 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.into()); + assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who), new_coldkey_hash, @@ -156,7 +203,6 @@ fn test_swap_coldkey_announced_works() { let hotkey1 = U256::from(1001); let hotkey2 = U256::from(1002); let hotkey3 = U256::from(1003); - let swap_cost = SubtensorModule::get_key_swap_cost(); let left_over = 12345; let min_stake = DefaultMinStake::::get().to_u64(); let stake1 = min_stake * 10; @@ -164,10 +210,7 @@ fn test_swap_coldkey_announced_works() { let stake3 = min_stake * 30; let now = System::block_number(); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_hash, - )); + ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash)); // Run some blocks for the announcement to be past the delay let delay = ColdkeySwapAnnouncementDelay::::get() + 1; @@ -188,7 +231,6 @@ fn test_swap_coldkey_announced_works() { who, new_coldkey, new_coldkey_hash, - swap_cost, left_over, stake1, stake2, @@ -212,7 +254,6 @@ fn test_swap_coldkey_announced_works() { new_coldkey, balance_before, left_over, - swap_cost, identity, netuid1, netuid2, @@ -263,11 +304,9 @@ fn test_swap_coldkey_announced_with_mismatched_coldkey_hash_fails() { let new_coldkey = U256::from(2); let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); let other_coldkey = U256::from(3); + let now = System::block_number(); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_hash, - )); + ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash)); assert_noop!( SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who), other_coldkey), @@ -282,11 +321,9 @@ fn test_swap_coldkey_announced_too_early_fails() { let who = U256::from(1); let new_coldkey = U256::from(2); let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let now = System::block_number(); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_hash, - )); + ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash)); assert_noop!( SubtensorModule::swap_coldkey_announced( @@ -306,6 +343,9 @@ fn test_swap_coldkey_announced_with_already_associated_coldkey_fails() { let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); let hotkey = U256::from(3); + let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost); + assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who), new_coldkey_hash, @@ -315,9 +355,6 @@ fn test_swap_coldkey_announced_with_already_associated_coldkey_fails() { let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.to_u64()); - SubtensorModule::create_account_if_non_existent(&new_coldkey, &hotkey); assert_noop!( @@ -337,19 +374,14 @@ fn test_swap_coldkey_announced_with_hotkey_fails() { let new_coldkey = U256::from(2); let hotkey = U256::from(3); let hotkey_hash = ::Hashing::hash_of(&hotkey); + let now = System::block_number(); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - hotkey_hash, - )); + ColdkeySwapAnnouncements::::insert(who.clone(), (now, hotkey_hash.clone())); let now = System::block_number(); let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); - let swap_cost = SubtensorModule::get_key_swap_cost(); - SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost.to_u64()); - SubtensorModule::create_account_if_non_existent(&new_coldkey, &hotkey); assert_noop!( @@ -371,13 +403,15 @@ fn test_swap_coldkey_works() { let hotkey1 = U256::from(1001); let hotkey2 = U256::from(1002); let hotkey3 = U256::from(1003); - let swap_cost = SubtensorModule::get_key_swap_cost(); let left_over = 12345; let min_stake = DefaultMinStake::::get().to_u64(); let stake1 = min_stake * 10; let stake2 = min_stake * 20; let stake3 = min_stake * 30; + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + let ( netuid1, netuid2, @@ -393,7 +427,6 @@ fn test_swap_coldkey_works() { old_coldkey, new_coldkey, new_coldkey_hash, - swap_cost, left_over, stake1, stake2, @@ -419,7 +452,6 @@ fn test_swap_coldkey_works() { new_coldkey, balance_before, left_over, - swap_cost, identity, netuid1, netuid2, @@ -480,11 +512,7 @@ fn test_do_swap_coldkey_preserves_new_coldkey_identity() { }; IdentitiesV2::::insert(new_coldkey, new_identity.clone()); - assert_ok!(SubtensorModule::do_swap_coldkey( - &who, - &new_coldkey, - TaoCurrency::ZERO - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&who, &new_coldkey,)); // Identity is preserved assert_eq!(IdentitiesV2::::get(who), Some(old_identity)); @@ -493,18 +521,14 @@ fn test_do_swap_coldkey_preserves_new_coldkey_identity() { } #[test] -fn test_do_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { +fn test_announce_coldkey_swap_with_not_enough_balance_to_pay_swap_cost_fails() { new_test_ext(1).execute_with(|| { let who = U256::from(1); let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let now = System::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get() + 1; - System::run_to_block::(now + delay); - - let swap_cost = SubtensorModule::get_key_swap_cost(); assert_noop!( - SubtensorModule::do_swap_coldkey(&who, &new_coldkey, swap_cost), + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_hash), Error::::NotEnoughBalanceToPaySwapColdKey ); }); @@ -516,11 +540,7 @@ fn test_do_swap_coldkey_with_no_stake() { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - TaoCurrency::ZERO - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), @@ -590,15 +610,10 @@ fn test_do_swap_coldkey_with_max_values() { netuid2, ); - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - TaoCurrency::ZERO - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_ok!(SubtensorModule::do_swap_coldkey( &old_coldkey2, &new_coldkey2, - TaoCurrency::ZERO )); assert_eq!( @@ -656,11 +671,7 @@ fn test_do_swap_coldkey_effect_on_delegated_stake() { let coldkey_stake_before = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); let delegator_stake_before = SubtensorModule::get_total_stake_for_coldkey(&delegator); - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - TaoCurrency::ZERO - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), @@ -753,11 +764,7 @@ fn test_swap_delegated_stake_for_coldkey() { let total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - TaoCurrency::ZERO - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Verify stake transfer assert_eq!( @@ -1051,11 +1058,7 @@ fn test_coldkey_swap_total() { SubtensorModule::get_total_stake_for_coldkey(&coldkey), ck_stake ); - assert_ok!(SubtensorModule::do_swap_coldkey( - &coldkey, - &new_coldkey, - TaoCurrency::ZERO - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), ck_stake @@ -1190,11 +1193,7 @@ fn test_do_swap_coldkey_effect_on_delegations() { )); // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey( - &coldkey, - &new_coldkey, - TaoCurrency::ZERO - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); // Verify stake was moved for the delegate let approx_total_stake = TaoCurrency::from(stake * 2 - fee * 2); @@ -1236,11 +1235,9 @@ fn test_remove_coldkey_swap_announcement_works() { let who = U256::from(1); let new_coldkey = U256::from(2); let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let now = System::block_number(); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_hash, - )); + ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash.clone())); assert_ok!(SubtensorModule::remove_coldkey_swap_announcement( RuntimeOrigin::root(), @@ -1410,7 +1407,6 @@ macro_rules! comprehensive_setup { $who:expr, $new_coldkey:expr, $new_coldkey_hash:expr, - $swap_cost:expr, $left_over:expr, $stake1:expr, $stake2:expr, @@ -1421,7 +1417,7 @@ macro_rules! comprehensive_setup { ) => {{ SubtensorModule::add_balance_to_coldkey_account( &$who, - $stake1 + $stake2 + $stake3 + $swap_cost.to_u64() + $left_over, + $stake1 + $stake2 + $stake3 + $left_over, ); // Setup networks and subnet ownerships @@ -1525,7 +1521,6 @@ macro_rules! comprehensive_checks { $new_coldkey:expr, $balance_before:expr, $left_over:expr, - $swap_cost:expr, $identity:expr, $netuid1:expr, $netuid2:expr, @@ -1538,12 +1533,12 @@ macro_rules! comprehensive_checks { // Ensure the announcement has been consumed assert!(!ColdkeySwapAnnouncements::::contains_key($who)); - // Ensure the cost has been withdrawn from the old coldkey and recycled - let balance_after = SubtensorModule::get_coldkey_balance(&$who); - assert_eq!( - $balance_before - $swap_cost.to_u64(), - balance_after + $left_over - ); + // // Ensure the cost has been withdrawn from the old coldkey and recycled + // let balance_after = SubtensorModule::get_coldkey_balance(&$who); + // assert_eq!( + // $balance_before - $swap_cost.to_u64(), + // balance_after + $left_over + // ); // Ensure the identity is correctly swapped assert!(IdentitiesV2::::get($who).is_none()); @@ -1649,7 +1644,6 @@ macro_rules! comprehensive_checks { Event::ColdkeySwapped { old_coldkey: $who, new_coldkey: $new_coldkey, - swap_cost: $swap_cost, } .into(), ); From 05951304f952d915ef00c79ebbb851594e260c36 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 11 Dec 2025 20:36:08 +0100 Subject: [PATCH 41/62] store when can be performed instead of when was announced --- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/macros/dispatches.rs | 19 ++++--------- pallets/subtensor/src/tests/swap_coldkey.rs | 31 +++++++++++---------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 9f0a28fed4..25f2f599a4 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1339,7 +1339,7 @@ pub mod pallet { StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapAnnouncementDelay>; /// A map of the coldkey swap announcements from a coldkey - /// to the block number the announcement was made and the new coldkey. + /// to the block number the coldkey swap can be performed. #[pallet::storage] pub type ColdkeySwapAnnouncements = StorageMap<_, Twox64Concat, T::AccountId, (BlockNumberFor, T::Hash), OptionQuery>; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 7368092e2a..620cb05715 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2375,19 +2375,16 @@ mod dispatches { let who = ensure_signed(origin)?; let now = >::block_number(); - if let Some(existing) = ColdkeySwapAnnouncements::::get(who.clone()) { - let delay = ColdkeySwapAnnouncementDelay::::get(); - let when = existing.0; - ensure!( - now > when.saturating_add(delay), - Error::::ColdkeySwapReannouncedTooEarly - ); + if let Some((when, _)) = ColdkeySwapAnnouncements::::get(who.clone()) { + ensure!(now >= when, Error::::ColdkeySwapReannouncedTooEarly); } else { let swap_cost = Self::get_key_swap_cost(); Self::charge_swap_cost(&who, swap_cost)?; } - ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash.clone())); + let delay = ColdkeySwapAnnouncementDelay::::get(); + let when = now.saturating_add(delay); + ColdkeySwapAnnouncements::::insert(who.clone(), (when, new_coldkey_hash.clone())); Self::deposit_event(Event::ColdkeySwapAnnounced { who, @@ -2425,11 +2422,7 @@ mod dispatches { ); let now = >::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get(); - ensure!( - now > when.saturating_add(delay), - Error::::ColdkeySwapTooEarly - ); + ensure!(now >= when, Error::::ColdkeySwapTooEarly); Self::do_swap_coldkey(&who, &new_coldkey)?; diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 6f003a8d0e..68b985b67f 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -53,9 +53,10 @@ fn test_announce_coldkey_swap_works() { )); let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who, (now, new_coldkey_hash))] + vec![(who, (now + delay, new_coldkey_hash))] ); assert_eq!(SubtensorModule::get_coldkey_balance(&who), left_over); assert_eq!( @@ -88,12 +89,12 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { )); let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who, (now, new_coldkey_hash))] + vec![(who, (now + delay, new_coldkey_hash))] ); - let delay = ColdkeySwapAnnouncementDelay::::get() + 1; System::run_to_block::(now + delay); assert_ok!(SubtensorModule::announce_coldkey_swap( @@ -104,7 +105,7 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { let now = System::block_number(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who, (now, new_coldkey_2_hash))] + vec![(who, (now + delay, new_coldkey_2_hash))] ); }); } @@ -118,19 +119,22 @@ fn test_announce_coldkey_swap_only_pays_swap_cost_if_no_announcement_exists() { let new_coldkey_2 = U256::from(3); let new_coldkey_2_hash = ::Hashing::hash_of(&new_coldkey_2); let left_over = 12345; - + let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost + left_over); - assert_eq!(SubtensorModule::get_coldkey_balance(&who), swap_cost + left_over); + assert_eq!( + SubtensorModule::get_coldkey_balance(&who), + swap_cost + left_over + ); assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who), new_coldkey_hash, )); assert_eq!(SubtensorModule::get_coldkey_balance(&who), left_over); - + let now = System::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get() + 1; + let delay = ColdkeySwapAnnouncementDelay::::get(); System::run_to_block::(now + delay); assert_ok!(SubtensorModule::announce_coldkey_swap( @@ -179,14 +183,12 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() )); let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); assert_eq!( ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who, (now, new_coldkey_hash))] + vec![(who, (now + delay, new_coldkey_hash))] ); - let unmet_delay = ColdkeySwapAnnouncementDelay::::get(); - System::run_to_block::(now + unmet_delay); - assert_noop!( SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_2_hash,), Error::::ColdkeySwapReannouncedTooEarly @@ -321,9 +323,10 @@ fn test_swap_coldkey_announced_too_early_fails() { let who = U256::from(1); let new_coldkey = U256::from(2); let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let now = System::block_number(); - ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash)); + let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); + ColdkeySwapAnnouncements::::insert(who.clone(), (now + delay, new_coldkey_hash)); assert_noop!( SubtensorModule::swap_coldkey_announced( From 12eb085ec3c21269c3fa7132c858ced42b9bfb9b Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 11 Dec 2025 20:58:52 +0100 Subject: [PATCH 42/62] reannouncement delay --- chain-extensions/src/mock.rs | 2 ++ pallets/admin-utils/src/lib.rs | 19 +++++++++++++++++++ pallets/admin-utils/src/tests/mock.rs | 2 ++ pallets/subtensor/src/lib.rs | 11 +++++++++++ pallets/subtensor/src/macros/config.rs | 9 +++++---- pallets/subtensor/src/macros/dispatches.rs | 4 +++- pallets/subtensor/src/macros/events.rs | 2 ++ pallets/subtensor/src/tests/mock.rs | 2 ++ pallets/subtensor/src/tests/swap_coldkey.rs | 8 +++++--- pallets/subtensor/src/utils/misc.rs | 5 +++++ pallets/transaction-fee/src/tests/mock.rs | 2 ++ runtime/src/lib.rs | 2 ++ 12 files changed, 60 insertions(+), 8 deletions(-) diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index 8a38fea2a7..b94b6f27f6 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -326,6 +326,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialColdkeySwapReannouncementDelay: u64 = 1 * 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -396,6 +397,7 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = Preimage; type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 6321f2363f..502e7777bb 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -2202,6 +2202,25 @@ pub mod pallet { log::trace!("ColdkeySwapAnnouncementDelaySet( duration: {duration:?} )"); Ok(()) } + + /// Sets the reannouncement delay for coldkey swap. + #[pallet::call_index(85)] + #[pallet::weight(( + Weight::from_parts(5_000_000, 0) + .saturating_add(T::DbWeight::get().reads(0_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn sudo_set_coldkey_swap_reannouncement_delay( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + ensure_root(origin)?; + pallet_subtensor::Pallet::::set_coldkey_swap_reannouncement_delay(duration); + log::trace!("ColdkeySwapReannouncementDelaySet( duration: {duration:?} )"); + Ok(()) + } } } diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index e49afe124f..bbc5954b1f 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -138,6 +138,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialColdkeySwapReannouncementDelay: u64 = 1 * 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -207,6 +208,7 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = (); type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 25f2f599a4..ef306927a5 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -950,6 +950,12 @@ pub mod pallet { T::InitialColdkeySwapAnnouncementDelay::get() } + /// Default value for coldkey swap reannouncement delay. + #[pallet::type_value] + pub fn DefaultColdkeySwapReannouncementDelay() -> BlockNumberFor { + T::InitialColdkeySwapReannouncementDelay::get() + } + /// Default value for applying pending items (e.g. childkeys). #[pallet::type_value] pub fn DefaultPendingCooldown() -> u64 { @@ -1338,6 +1344,11 @@ pub mod pallet { pub type ColdkeySwapAnnouncementDelay = StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapAnnouncementDelay>; + /// The delay after a reannouncement before a coldkey swap can be performed. + #[pallet::storage] + pub type ColdkeySwapReannouncementDelay = + StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapReannouncementDelay>; + /// A map of the coldkey swap announcements from a coldkey /// to the block number the coldkey swap can be performed. #[pallet::storage] diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 6f8b5466ee..ee42feb117 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -214,13 +214,14 @@ mod config { #[pallet::constant] type LiquidAlphaOn: Get; /// A flag to indicate if Yuma3 is enabled. + #[pallet::constant] type Yuma3On: Get; - // /// Initial hotkey emission tempo. - // #[pallet::constant] - // type InitialHotkeyEmissionTempo: Get; - /// Coldkey swap schedule duartion. + /// Coldkey swap announcement delay. #[pallet::constant] type InitialColdkeySwapAnnouncementDelay: Get>; + /// Coldkey swap reannouncement delay. + #[pallet::constant] + type InitialColdkeySwapReannouncementDelay: Get>; /// Dissolve network schedule duration #[pallet::constant] type InitialDissolveNetworkScheduleDuration: Get>; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 620cb05715..f23c78398d 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2376,7 +2376,9 @@ mod dispatches { let now = >::block_number(); if let Some((when, _)) = ColdkeySwapAnnouncements::::get(who.clone()) { - ensure!(now >= when, Error::::ColdkeySwapReannouncedTooEarly); + let reannouncement_delay = ColdkeySwapReannouncementDelay::::get(); + let new_when = when.saturating_add(reannouncement_delay); + ensure!(now >= new_when, Error::::ColdkeySwapReannouncedTooEarly); } else { let swap_cost = Self::get_key_swap_cost(); Self::charge_swap_cost(&who, swap_cost)?; diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 793b30b217..d45effe738 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -230,6 +230,8 @@ mod events { }, /// The coldkey swap announcement delay has been set. ColdkeySwapAnnouncementDelaySet(BlockNumberFor), + /// The coldkey swap reannouncement delay has been set. + ColdkeySwapReannouncementDelaySet(BlockNumberFor), /// The duration of dissolve network has been set DissolveNetworkScheduleDurationSet(BlockNumberFor), /// Commit-reveal v3 weights have been successfully committed. diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index d0830198b9..f021d9d721 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -213,6 +213,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On pub const InitialColdkeySwapAnnouncementDelay: u64 = 50; + pub const InitialColdkeySwapReannouncementDelay: u64 = 10; pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -283,6 +284,7 @@ impl crate::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = Preimage; type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 68b985b67f..a2ac22263e 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -95,7 +95,8 @@ fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { vec![(who, (now + delay, new_coldkey_hash))] ); - System::run_to_block::(now + delay); + let reannouncement_delay = ColdkeySwapReannouncementDelay::::get(); + System::run_to_block::(now + delay + reannouncement_delay); assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who), @@ -134,8 +135,9 @@ fn test_announce_coldkey_swap_only_pays_swap_cost_if_no_announcement_exists() { assert_eq!(SubtensorModule::get_coldkey_balance(&who), left_over); let now = System::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get(); - System::run_to_block::(now + delay); + let base_delay = ColdkeySwapAnnouncementDelay::::get(); + let reannouncement_delay = ColdkeySwapReannouncementDelay::::get(); + System::run_to_block::(now + base_delay + reannouncement_delay); assert_ok!(SubtensorModule::announce_coldkey_swap( RuntimeOrigin::signed(who), diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 772d825541..4dad15d8fe 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -824,6 +824,11 @@ impl Pallet { Self::deposit_event(Event::ColdkeySwapAnnouncementDelaySet(duration)); } + pub fn set_coldkey_swap_reannouncement_delay(duration: BlockNumberFor) { + ColdkeySwapReannouncementDelay::::set(duration); + Self::deposit_event(Event::ColdkeySwapReannouncementDelaySet(duration)); + } + /// Set the duration for dissolve network /// /// # Arguments diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 4ebef59c38..84c7eeee9f 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -203,6 +203,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialColdkeySwapReannouncementDelay: u64 = 1 * 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -272,6 +273,7 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = (); type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 0ac0fb0787..3e721940d6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1047,6 +1047,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On pub const InitialColdkeySwapAnnouncementDelay: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialColdkeySwapReannouncementDelay: BlockNumber = 1 * 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -1117,6 +1118,7 @@ impl pallet_subtensor::Config for Runtime { type InitialTaoWeight = SubtensorInitialTaoWeight; type Preimages = Preimage; type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall; From 61acd0f1b7063e9b820800727509091c1f3d0105 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 11 Dec 2025 21:00:36 +0100 Subject: [PATCH 43/62] fix test --- pallets/admin-utils/src/tests/mod.rs | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 5fcfeed083..a901070705 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1417,6 +1417,42 @@ fn test_sudo_set_coldkey_swap_announcement_delay() { }); } +#[test] +fn test_sudo_set_coldkey_swap_reannouncement_delay() { + new_test_ext().execute_with(|| { + // Arrange + let root = RuntimeOrigin::root(); + let non_root = RuntimeOrigin::signed(U256::from(1)); + let new_delay = 100u32.into(); + + // Act & Assert: Non-root account should fail + assert_noop!( + AdminUtils::sudo_set_coldkey_swap_reannouncement_delay(non_root, new_delay), + DispatchError::BadOrigin + ); + + // Act: Root account should succeed + assert_ok!(AdminUtils::sudo_set_coldkey_swap_reannouncement_delay( + root.clone(), + new_delay + )); + + // Assert: Check if the delay was actually set + assert_eq!( + pallet_subtensor::ColdkeySwapReannouncementDelay::::get(), + new_delay + ); + + // Act & Assert: Setting the same value again should succeed (idempotent operation) + assert_ok!(AdminUtils::sudo_set_coldkey_swap_reannouncement_delay( + root, new_delay + )); + + // You might want to check for events here if your pallet emits them + System::assert_last_event(Event::ColdkeySwapReannouncementDelaySet(new_delay).into()); + }); +} + #[test] fn test_sudo_set_dissolve_network_schedule_duration() { new_test_ext().execute_with(|| { From fc4224075c0e9cfe54a30cbef6b8fb9183326d0c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 12 Dec 2025 12:18:55 +0100 Subject: [PATCH 44/62] fix rust --- chain-extensions/src/mock.rs | 2 +- pallets/admin-utils/src/tests/mock.rs | 2 +- pallets/subtensor/src/tests/swap_coldkey.rs | 10 +++++----- pallets/transaction-fee/src/tests/mock.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index b94b6f27f6..3583169d91 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -326,7 +326,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days - pub const InitialColdkeySwapReannouncementDelay: u64 = 1 * 24 * 60 * 60 / 12; // Default as 1 day + pub const InitialColdkeySwapReannouncementDelay: u64 = 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index bbc5954b1f..7d61b55a5f 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -138,7 +138,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days - pub const InitialColdkeySwapReannouncementDelay: u64 = 1 * 24 * 60 * 60 / 12; // 1 day + pub const InitialColdkeySwapReannouncementDelay: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index a2ac22263e..e9b6cf4c0b 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -214,7 +214,7 @@ fn test_swap_coldkey_announced_works() { let stake3 = min_stake * 30; let now = System::block_number(); - ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash)); + ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); // Run some blocks for the announcement to be past the delay let delay = ColdkeySwapAnnouncementDelay::::get() + 1; @@ -310,7 +310,7 @@ fn test_swap_coldkey_announced_with_mismatched_coldkey_hash_fails() { let other_coldkey = U256::from(3); let now = System::block_number(); - ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash)); + ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); assert_noop!( SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who), other_coldkey), @@ -328,7 +328,7 @@ fn test_swap_coldkey_announced_too_early_fails() { let now = System::block_number(); let delay = ColdkeySwapAnnouncementDelay::::get(); - ColdkeySwapAnnouncements::::insert(who.clone(), (now + delay, new_coldkey_hash)); + ColdkeySwapAnnouncements::::insert(who, (now + delay, new_coldkey_hash)); assert_noop!( SubtensorModule::swap_coldkey_announced( @@ -381,7 +381,7 @@ fn test_swap_coldkey_announced_with_hotkey_fails() { let hotkey_hash = ::Hashing::hash_of(&hotkey); let now = System::block_number(); - ColdkeySwapAnnouncements::::insert(who.clone(), (now, hotkey_hash.clone())); + ColdkeySwapAnnouncements::::insert(who, (now, hotkey_hash)); let now = System::block_number(); let delay = ColdkeySwapAnnouncementDelay::::get() + 1; @@ -1242,7 +1242,7 @@ fn test_remove_coldkey_swap_announcement_works() { let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); let now = System::block_number(); - ColdkeySwapAnnouncements::::insert(who.clone(), (now, new_coldkey_hash.clone())); + ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); assert_ok!(SubtensorModule::remove_coldkey_swap_announcement( RuntimeOrigin::root(), diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 84c7eeee9f..d008c7fd0c 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -203,7 +203,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On pub const InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days - pub const InitialColdkeySwapReannouncementDelay: u64 = 1 * 24 * 60 * 60 / 12; // 1 day + pub const InitialColdkeySwapReannouncementDelay: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks From c415d8bc63197a8000de0513d64b5126244c4244 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 12 Dec 2025 15:25:03 +0100 Subject: [PATCH 45/62] cargo clippy --- 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 a95ef01552..63cc67ca27 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1047,7 +1047,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On pub const InitialColdkeySwapAnnouncementDelay: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days - pub const InitialColdkeySwapReannouncementDelay: BlockNumber = 1 * 24 * 60 * 60 / 12; // 1 day + pub const InitialColdkeySwapReannouncementDelay: BlockNumber = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks From a54ac00cedb36c0ba7cbd53a9ee524a555d9eb9b Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 12 Dec 2025 23:06:33 +0100 Subject: [PATCH 46/62] fix benchmark --- pallets/subtensor/src/benchmarks.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index a616211407..836801f764 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -413,6 +413,9 @@ mod pallet_benchmarks { let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); let new_coldkey_hash: T::Hash = ::Hashing::hash_of(&new_coldkey); + let swap_cost = Subtensor::::get_key_swap_cost(); + Subtensor::::add_balance_to_coldkey_account(&coldkey, swap_cost.into()); + #[extrinsic_call] _(RawOrigin::Signed(coldkey), new_coldkey_hash); } @@ -429,9 +432,6 @@ mod pallet_benchmarks { ColdkeySwapAnnouncements::::insert(&old_coldkey, (now, new_coldkey_hash)); frame_system::Pallet::::set_block_number(now + delay + 1u32.into()); - let swap_cost = Subtensor::::get_key_swap_cost(); - Subtensor::::add_balance_to_coldkey_account(&old_coldkey, swap_cost.into()); - let netuid = NetUid::from(1); Subtensor::::init_new_network(netuid, 1); Subtensor::::set_network_registration_allowed(netuid, true); From 92365943a6d2a8a9981fd79ed5f20ee1ccb04017 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sat, 13 Dec 2025 15:22:02 +0100 Subject: [PATCH 47/62] fix call indices --- pallets/admin-utils/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 3f4b11da34..34e6f4a66a 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -2204,7 +2204,7 @@ pub mod pallet { } /// Sets the announcement delay for coldkey swap. - #[pallet::call_index(84)] + #[pallet::call_index(85)] #[pallet::weight(( Weight::from_parts(5_000_000, 0) .saturating_add(T::DbWeight::get().reads(0_u64)) @@ -2223,7 +2223,7 @@ pub mod pallet { } /// Sets the reannouncement delay for coldkey swap. - #[pallet::call_index(85)] + #[pallet::call_index(86)] #[pallet::weight(( Weight::from_parts(5_000_000, 0) .saturating_add(T::DbWeight::get().reads(0_u64)) From 1079b3cbd3532a8557d826f0a15c942602dd0ef1 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sun, 14 Dec 2025 20:05:19 +0100 Subject: [PATCH 48/62] fix missing return --- pallets/admin-utils/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 34e6f4a66a..875916ccb7 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -2238,6 +2238,7 @@ pub mod pallet { ensure_root(origin)?; pallet_subtensor::Pallet::::set_coldkey_swap_reannouncement_delay(duration); log::trace!("ColdkeySwapReannouncementDelaySet( duration: {duration:?} )"); + Ok(()) } } } From 2d9036f643bb03bddbe71626fe165c9f35cd4759 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Dec 2025 19:31:58 +0100 Subject: [PATCH 49/62] fix tests --- pallets/subtensor/src/tests/leasing.rs | 212 +++++++++++++------------ 1 file changed, 112 insertions(+), 100 deletions(-) diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 9f8bf4d6bf..77bc4e4c73 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -4,7 +4,7 @@ clippy::indexing_slicing )] use super::mock::*; -use crate::{subnets::leasing::SubnetLeaseOf, *}; +use crate::*; use frame_support::{StorageDoubleMap, assert_err, assert_ok}; use sp_core::U256; use sp_runtime::Percent; @@ -23,7 +23,7 @@ fn test_register_leased_network_works() { (U256::from(2), 600_000_000_000), // 600 TAO (U256::from(3), 390_000_000_000), // 390 TAO ]; - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Register the leased network let end_block = 500; @@ -163,7 +163,7 @@ fn test_register_lease_network_fails_if_current_crowdloan_id_is_not_set() { (U256::from(2), 600_000_000_000), // 600 TAO (U256::from(3), 390_000_000_000), // 390 TAO ]; - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Mark as if the current crowdloan id is not set pallet_crowdloan::CurrentCrowdloanId::::set(None); @@ -194,7 +194,7 @@ fn test_register_leased_network_fails_if_origin_is_not_crowdloan_creator() { (U256::from(2), 600_000_000_000), // 600 TAO (U256::from(3), 390_000_000_000), // 390 TAO ]; - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); let end_block = 500; let emissions_share = Percent::from_percent(30); @@ -221,7 +221,7 @@ fn test_register_lease_network_fails_if_end_block_is_in_the_past() { (U256::from(2), 600_000_000_000), // 600 TAO (U256::from(3), 390_000_000_000), // 390 TAO ]; - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); let end_block = 500; let emissions_share = Percent::from_percent(30); @@ -246,17 +246,17 @@ fn test_terminate_lease_works() { let deposit = 10_000_000_000; // 10 TAO let cap = 1_000_000_000_000; // 1000 TAO let contributions = vec![(U256::from(2), 990_000_000_000)]; // 990 TAO - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let tao_to_stake = 100_000_000_000; // 100 TAO let emissions_share = Percent::from_percent(30); - let (lease_id, lease) = setup_leased_network( + let (lease_id, lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - Some(tao_to_stake), + Some(tao_to_stake) ); // Run to the end of the lease @@ -338,17 +338,17 @@ fn test_terminate_lease_fails_if_origin_is_not_beneficiary() { let deposit = 10_000_000_000; // 10 TAO let cap = 1_000_000_000_000; // 1000 TAO let contributions = vec![(U256::from(2), 990_000_000_000)]; // 990 TAO - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let tao_to_stake = 100_000_000_000; // 100 TAO let emissions_share = Percent::from_percent(30); - let (lease_id, _lease) = setup_leased_network( + let (lease_id, _lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - Some(tao_to_stake), + Some(tao_to_stake) ); // Run to the end of the lease @@ -379,13 +379,13 @@ fn test_terminate_lease_fails_if_lease_has_no_end_block() { let deposit = 10_000_000_000; // 10 TAO let cap = 1_000_000_000_000; // 1000 TAO let contributions = vec![(U256::from(2), 990_000_000_000)]; // 990 TAO - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let tao_to_stake = 100_000_000_000; // 100 TAO let emissions_share = Percent::from_percent(30); let (lease_id, lease) = - setup_leased_network(beneficiary, emissions_share, None, Some(tao_to_stake)); + setup_leased_network!(beneficiary, emissions_share, None, Some(tao_to_stake)); // Create a hotkey for the beneficiary let hotkey = U256::from(3); @@ -412,17 +412,17 @@ fn test_terminate_lease_fails_if_lease_has_not_ended() { let deposit = 10_000_000_000; // 10 TAO let cap = 1_000_000_000_000; // 1000 TAO let contributions = vec![(U256::from(2), 990_000_000_000)]; // 990 TAO - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let tao_to_stake = 100_000_000_000; // 100 TAO let emissions_share = Percent::from_percent(30); - let (lease_id, lease) = setup_leased_network( + let (lease_id, lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - Some(tao_to_stake), + Some(tao_to_stake) ); // Create a hotkey for the beneficiary @@ -450,17 +450,17 @@ fn test_terminate_lease_fails_if_beneficiary_does_not_own_hotkey() { let deposit = 10_000_000_000; // 10 TAO let cap = 1_000_000_000_000; // 1000 TAO let contributions = vec![(U256::from(2), 990_000_000_000)]; // 990 TAO - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let tao_to_stake = 100_000_000_000; // 100 TAO let emissions_share = Percent::from_percent(30); - let (lease_id, lease) = setup_leased_network( + let (lease_id, lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - Some(tao_to_stake), + Some(tao_to_stake) ); // Run to the end of the lease @@ -489,17 +489,17 @@ fn test_distribute_lease_network_dividends_multiple_contributors_works() { (U256::from(2), 600_000_000_000), // 600 TAO (U256::from(3), 390_000_000_000), // 390 TAO ]; - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let emissions_share = Percent::from_percent(30); let tao_to_stake = 100_000_000_000; // 100 TAO - let (lease_id, lease) = setup_leased_network( + let (lease_id, lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - Some(tao_to_stake), + Some(tao_to_stake) ); // Setup the correct block to distribute dividends @@ -628,17 +628,17 @@ fn test_distribute_lease_network_dividends_only_beneficiary_works() { let deposit = 10_000_000_000; // 10 TAO let cap = 1_000_000_000_000; // 1000 TAO let contributions = vec![(U256::from(1), 990_000_000_000)]; // 990 TAO - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let emissions_share = Percent::from_percent(30); let tao_to_stake = 100_000_000_000; // 100 TAO - let (lease_id, lease) = setup_leased_network( + let (lease_id, lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - Some(tao_to_stake), + Some(tao_to_stake) ); // Setup the correct block to distribute dividends @@ -699,17 +699,17 @@ fn test_distribute_lease_network_dividends_accumulates_if_not_the_correct_block( (U256::from(2), 600_000_000_000), // 600 TAO (U256::from(3), 390_000_000_000), // 390 TAO ]; - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let emissions_share = Percent::from_percent(30); let tao_to_stake = 100_000_000_000; // 100 TAO - let (lease_id, lease) = setup_leased_network( + let (lease_id, lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - Some(tao_to_stake), + Some(tao_to_stake) ); // Setup incorrect block to distribute dividends @@ -799,17 +799,17 @@ fn test_distribute_lease_network_dividends_does_nothing_if_lease_has_ended() { (U256::from(2), 600_000_000_000), // 600 TAO (U256::from(3), 390_000_000_000), // 390 TAO ]; - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let tao_to_stake = 100_000_000_000; // 100 TAO let emissions_share = Percent::from_percent(30); - let (lease_id, lease) = setup_leased_network( + let (lease_id, lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - Some(tao_to_stake), + Some(tao_to_stake) ); // Run to the end of the lease @@ -889,16 +889,16 @@ fn test_distribute_lease_network_dividends_accumulates_if_amount_is_too_low() { (U256::from(3), 390_000_000_000), // 390 TAO ]; - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let emissions_share = Percent::from_percent(30); - let (lease_id, lease) = setup_leased_network( + let (lease_id, lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - None, // We don't add any liquidity + None // We don't add any liquidity ); // Get the initial alpha for the contributors and beneficiary and ensure they are zero @@ -971,16 +971,16 @@ fn test_distribute_lease_network_dividends_accumulates_if_insufficient_liquidity (U256::from(3), 390_000_000_000), // 390 TAO ]; - setup_crowdloan(crowdloan_id, deposit, cap, beneficiary, &contributions); + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); // Setup a leased network let end_block = 500; let emissions_share = Percent::from_percent(30); - let (lease_id, lease) = setup_leased_network( + let (lease_id, lease) = setup_leased_network!( beneficiary, emissions_share, Some(end_block), - None, // We don't add any liquidity + None // We don't add any liquidity ); let contributor1_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -1039,71 +1039,83 @@ fn test_distribute_lease_network_dividends_accumulates_if_insufficient_liquidity }); } -fn setup_crowdloan( - id: u32, - deposit: u64, - cap: u64, - beneficiary: U256, - contributions: &[(U256, u64)], -) { - let funds_account = U256::from(42424242 + id); - - pallet_crowdloan::Crowdloans::::insert( - id, - pallet_crowdloan::CrowdloanInfo { - creator: beneficiary, - deposit, - min_contribution: 0, - end: 0, - cap, - raised: cap, - finalized: false, - funds_account, - call: None, - target_address: None, - contributors_count: 1 + contributions.len() as u32, - }, - ); - - // Simulate contributions - pallet_crowdloan::Contributions::::insert(id, beneficiary, deposit); - for (contributor, amount) in contributions { - pallet_crowdloan::Contributions::::insert(id, contributor, amount); - } - - SubtensorModule::add_balance_to_coldkey_account(&funds_account, cap); - - // Mark the crowdloan as finalizing - pallet_crowdloan::CurrentCrowdloanId::::set(Some(0)); +#[test] +fn announce_subnet_sale_into_lease_works() { + new_test_ext(1).execute_with(|| { + // Setup a crowdloan + let crowdloan_id = 0; + let beneficiary = U256::from(1); + let deposit = 10_000_000_000; // 10 TAO + let cap = 1_000_000_000_000; // 1000 TAO + let contributions = vec![ + (U256::from(2), 600_000_000_000), // 600 TAO + (U256::from(3), 390_000_000_000), // 390 TAO + ]; + + setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); + }); } -fn setup_leased_network( - beneficiary: U256, - emissions_share: Percent, - end_block: Option, - tao_to_stake: Option, -) -> (u32, SubnetLeaseOf) { - let lease_id = 0; - assert_ok!(SubtensorModule::do_register_leased_network( - RuntimeOrigin::signed(beneficiary), - emissions_share, - end_block, - )); - - // Configure subnet and add some stake - let lease = SubnetLeases::::get(lease_id).unwrap(); - let netuid = lease.netuid; - SubtokenEnabled::::insert(netuid, true); - - if let Some(tao_to_stake) = tao_to_stake { - SubtensorModule::add_balance_to_coldkey_account(&lease.coldkey, tao_to_stake); - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(lease.coldkey), - lease.hotkey, - netuid, - tao_to_stake.into() +#[macro_export] +macro_rules! setup_crowdloan { + ($id:expr, $deposit:expr, $cap:expr, $creator:expr, $contributions:expr) => { + let funds_account = U256::from(42424242 + $id); + + pallet_crowdloan::Crowdloans::::insert( + $id, + pallet_crowdloan::CrowdloanInfo { + creator: $creator, + deposit: $deposit, + min_contribution: 0, + end: 0, + cap: $cap, + raised: $cap, + finalized: false, + funds_account, + call: None, + target_address: None, + contributors_count: 1 + $contributions.len() as u32, + }, + ); + + // Simulate contributions + pallet_crowdloan::Contributions::::insert($id, $creator, $deposit); + for (contributor, amount) in $contributions { + pallet_crowdloan::Contributions::::insert($id, contributor, amount); + } + + SubtensorModule::add_balance_to_coldkey_account(&funds_account, $cap); + + // Mark the crowdloan as finalizing + pallet_crowdloan::CurrentCrowdloanId::::set(Some($id)); + }; +} + +#[macro_export] +macro_rules! setup_leased_network { + ($beneficiary:expr, $emissions_share:expr, $end_block:expr, $tao_to_stake:expr) => {{ + let lease_id = 0; + assert_ok!(SubtensorModule::do_register_leased_network( + RuntimeOrigin::signed($beneficiary), + $emissions_share, + $end_block, )); - } - (lease_id, lease) + // Configure subnet and add some stake + let lease = SubnetLeases::::get(lease_id).unwrap(); + let netuid = lease.netuid; + SubtokenEnabled::::insert(netuid, true); + + if let Some(tao_to_stake) = $tao_to_stake { + SubtensorModule::add_balance_to_coldkey_account(&lease.coldkey, tao_to_stake); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(lease.coldkey), + lease.hotkey, + netuid, + tao_to_stake.into() + )); + } + + (lease_id, lease) + }}; } From a0221f7e24e1f0114add22929cb77e4cf65854c5 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Dec 2025 15:16:03 +0100 Subject: [PATCH 50/62] added sale into lease announcement logic --- pallets/crowdloan/src/lib.rs | 2 + pallets/subtensor/src/lib.rs | 12 ++ pallets/subtensor/src/macros/dispatches.rs | 55 ++++++ pallets/subtensor/src/macros/errors.rs | 6 + pallets/subtensor/src/macros/events.rs | 10 + pallets/subtensor/src/subnets/leasing.rs | 2 +- pallets/subtensor/src/tests/leasing.rs | 218 +++++++++++++++++---- 7 files changed, 263 insertions(+), 42 deletions(-) diff --git a/pallets/crowdloan/src/lib.rs b/pallets/crowdloan/src/lib.rs index 82b1402bc6..fa7db153df 100644 --- a/pallets/crowdloan/src/lib.rs +++ b/pallets/crowdloan/src/lib.rs @@ -269,6 +269,8 @@ pub mod pallet { DepositCannotBeWithdrawn, /// The maximum number of contributors has been reached. MaxContributorsReached, + /// The current crowdloan ID is not set. + CurrentCrowdloanIdNotSet, } #[pallet::hooks] diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b9d3810fe5..153659d3bb 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -86,6 +86,7 @@ pub mod pallet { }, }; use frame_system::pallet_prelude::*; + use pallet_crowdloan::CrowdloanId; use pallet_drand::types::RoundNumber; use runtime_common::prod_or_fast; use sp_core::{ConstU32, H160, H256}; @@ -2275,6 +2276,17 @@ pub mod pallet { pub type AccumulatedLeaseDividends = StorageMap<_, Twox64Concat, LeaseId, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; + /// A map of the subnet sale into lease announcements from a coldkey to the + /// block number the announcement was made and the lease beneficiary. + #[pallet::storage] + pub type SubnetSaleIntoLeaseAnnouncements = StorageMap< + _, + Twox64Concat, + T::AccountId, + (BlockNumberFor, T::AccountId, NetUid, CrowdloanId), + OptionQuery, + >; + /// --- ITEM ( CommitRevealWeightsVersion ) #[pallet::storage] pub type CommitRevealWeightsVersion = diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 01f26f0a8d..d3ace76ab2 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2451,5 +2451,60 @@ mod dispatches { ColdkeySwapAnnouncements::::remove(coldkey); Ok(()) } + + /// Announces a subnet sale into a lease using the beneficiary coldkey. + /// + /// Only callable by subnet owner and through a crowdloan. + #[pallet::call_index(128)] + #[pallet::weight(Weight::zero())] + pub fn announce_subnet_sale_into_lease( + origin: OriginFor, + netuid: NetUid, + beneficiary: T::AccountId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + + let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() + .ok_or(pallet_crowdloan::Error::::CurrentCrowdloanIdNotSet)?; + + ensure!( + !ColdkeySwapAnnouncements::::contains_key(&who), + Error::::ColdkeySwapAnnouncementAlreadyExists + ); + ensure!( + !SubnetSaleIntoLeaseAnnouncements::::contains_key(&who), + Error::::SubnetSaleIntoLeaseAnnouncementAlreadyExists + ); + + ensure!( + SubnetOwner::::get(netuid) == who, + Error::::NotSubnetOwner + ); + let owners = SubnetOwner::::iter().collect::>(); + ensure!( + owners.iter().filter(|(_, owner)| owner == &who).count() == 1, + Error::::TooManySubnetsOwned + ); + + SubnetSaleIntoLeaseAnnouncements::::insert( + &who, + (now, beneficiary.clone(), netuid, crowdloan_id), + ); + + Self::deposit_event(Event::SubnetSaleIntoLeaseAnnounced { + who, + beneficiary, + netuid, + }); + Ok(()) + } + + /// Settles a subnet sale into a lease. + #[pallet::call_index(129)] + #[pallet::weight(Weight::zero())] + pub fn settle_subnet_sale_into_lease(_origin: OriginFor) -> DispatchResult { + Ok(()) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 174d9c547f..d5a854ba76 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -154,6 +154,8 @@ mod errors { ColdkeySwapTooEarly, /// Coldkey swap reannounced too early. ColdkeySwapReannouncedTooEarly, + /// Coldkey swap announcement already exists + ColdkeySwapAnnouncementAlreadyExists, /// The announced coldkey hash does not match the new coldkey hash. AnnouncedColdkeyHashDoesNotMatch, /// New coldkey is hotkey @@ -270,5 +272,9 @@ mod errors { InvalidSubnetNumber, /// Deprecated call. Deprecated, + /// Subnet sale into lease announcement already exists + SubnetSaleIntoLeaseAnnouncementAlreadyExists, + /// Too many subnets owned + TooManySubnetsOwned, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 0d7818d752..764e2d8430 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -480,5 +480,15 @@ mod events { /// The amount of alpha distributed alpha: AlphaCurrency, }, + + /// A subnet sale into lease announcement has been made. + SubnetSaleIntoLeaseAnnounced { + /// The account ID of the coldkey that made the announcement. + who: T::AccountId, + /// The account ID of the beneficiary. + beneficiary: T::AccountId, + /// The network identifier. + netuid: NetUid, + }, } } diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index a202a22a45..12b7be7290 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -385,7 +385,7 @@ impl Pallet { pallet_crowdloan::Error, > { let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() - .ok_or(pallet_crowdloan::Error::::InvalidCrowdloanId)?; + .ok_or(pallet_crowdloan::Error::::CurrentCrowdloanIdNotSet)?; let crowdloan = pallet_crowdloan::Crowdloans::::get(crowdloan_id) .ok_or(pallet_crowdloan::Error::::InvalidCrowdloanId)?; Ok((crowdloan_id, crowdloan)) diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 77bc4e4c73..f76b60a44c 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -5,9 +5,9 @@ )] use super::mock::*; use crate::*; -use frame_support::{StorageDoubleMap, assert_err, assert_ok}; +use frame_support::{StorageDoubleMap, assert_noop, assert_ok}; use sp_core::U256; -use sp_runtime::Percent; +use sp_runtime::{Percent, traits::Hash}; use substrate_fixed::types::U64F64; use subtensor_runtime_common::AlphaCurrency; @@ -94,16 +94,12 @@ fn test_register_leased_network_works() { ); // Ensure the event is emitted - assert_eq!( - last_event(), - crate::Event::::SubnetLeaseCreated { - beneficiary, - lease_id, - netuid: lease.netuid, - end_block: Some(end_block), - } - .into() - ); + assert_last_event::(RuntimeEvent::SubtensorModule(Event::SubnetLeaseCreated { + beneficiary, + lease_id, + netuid: lease.netuid, + end_block: Some(end_block), + })); }); } @@ -113,7 +109,7 @@ fn test_register_leased_network_fails_if_bad_origin() { let end_block = 500; let emissions_share = Percent::from_percent(30); - assert_err!( + assert_noop!( SubtensorModule::register_leased_network( RuntimeOrigin::none(), emissions_share, @@ -122,7 +118,7 @@ fn test_register_leased_network_fails_if_bad_origin() { DispatchError::BadOrigin, ); - assert_err!( + assert_noop!( SubtensorModule::register_leased_network( RuntimeOrigin::root(), emissions_share, @@ -140,7 +136,9 @@ fn test_register_leased_network_fails_if_crowdloan_does_not_exists() { let end_block = 500; let emissions_share = Percent::from_percent(30); - assert_err!( + pallet_crowdloan::CurrentCrowdloanId::::set(Some(0)); + + assert_noop!( SubtensorModule::register_leased_network( RuntimeOrigin::signed(beneficiary), emissions_share, @@ -171,13 +169,13 @@ fn test_register_lease_network_fails_if_current_crowdloan_id_is_not_set() { let end_block = 500; let emissions_share = Percent::from_percent(30); - assert_err!( + assert_noop!( SubtensorModule::register_leased_network( RuntimeOrigin::signed(beneficiary), emissions_share, Some(end_block), ), - pallet_crowdloan::Error::::InvalidCrowdloanId, + pallet_crowdloan::Error::::CurrentCrowdloanIdNotSet, ); }); } @@ -199,7 +197,7 @@ fn test_register_leased_network_fails_if_origin_is_not_crowdloan_creator() { let end_block = 500; let emissions_share = Percent::from_percent(30); - assert_err!( + assert_noop!( SubtensorModule::register_leased_network( RuntimeOrigin::signed(U256::from(2)), emissions_share, @@ -226,7 +224,7 @@ fn test_register_lease_network_fails_if_end_block_is_in_the_past() { let end_block = 500; let emissions_share = Percent::from_percent(30); - assert_err!( + assert_noop!( SubtensorModule::register_leased_network( RuntimeOrigin::signed(beneficiary), emissions_share, @@ -286,14 +284,12 @@ fn test_terminate_lease_works() { assert!(PROXIES.with_borrow(|proxies| proxies.0.is_empty())); // Ensure the event is emitted - assert_eq!( - last_event(), - crate::Event::::SubnetLeaseTerminated { + assert_last_event::(RuntimeEvent::SubtensorModule( + Event::::SubnetLeaseTerminated { beneficiary: lease.beneficiary, netuid: lease.netuid, - } - .into() - ); + }, + )); }); } @@ -303,12 +299,12 @@ fn test_terminate_lease_fails_if_bad_origin() { let lease_id = 0; let hotkey = U256::from(1); - assert_err!( + assert_noop!( SubtensorModule::terminate_lease(RuntimeOrigin::none(), lease_id, hotkey), DispatchError::BadOrigin, ); - assert_err!( + assert_noop!( SubtensorModule::terminate_lease(RuntimeOrigin::root(), lease_id, hotkey), DispatchError::BadOrigin, ); @@ -322,7 +318,7 @@ fn test_terminate_lease_fails_if_lease_does_not_exist() { let beneficiary = U256::from(1); let hotkey = U256::from(2); - assert_err!( + assert_noop!( SubtensorModule::terminate_lease(RuntimeOrigin::signed(beneficiary), lease_id, hotkey), Error::::LeaseDoesNotExist, ); @@ -359,7 +355,7 @@ fn test_terminate_lease_fails_if_origin_is_not_beneficiary() { SubtensorModule::create_account_if_non_existent(&beneficiary, &hotkey); // Terminate the lease - assert_err!( + assert_noop!( SubtensorModule::terminate_lease( RuntimeOrigin::signed(U256::from(42)), lease_id, @@ -392,7 +388,7 @@ fn test_terminate_lease_fails_if_lease_has_no_end_block() { SubtensorModule::create_account_if_non_existent(&beneficiary, &hotkey); // Terminate the lease - assert_err!( + assert_noop!( SubtensorModule::terminate_lease( RuntimeOrigin::signed(lease.beneficiary), lease_id, @@ -430,7 +426,7 @@ fn test_terminate_lease_fails_if_lease_has_not_ended() { SubtensorModule::create_account_if_non_existent(&beneficiary, &hotkey); // Terminate the lease - assert_err!( + assert_noop!( SubtensorModule::terminate_lease( RuntimeOrigin::signed(lease.beneficiary), lease_id, @@ -467,7 +463,7 @@ fn test_terminate_lease_fails_if_beneficiary_does_not_own_hotkey() { run_to_block(end_block); // Terminate the lease - assert_err!( + assert_noop!( SubtensorModule::terminate_lease( RuntimeOrigin::signed(lease.beneficiary), lease_id, @@ -1040,19 +1036,159 @@ fn test_distribute_lease_network_dividends_accumulates_if_insufficient_liquidity } #[test] -fn announce_subnet_sale_into_lease_works() { +fn test_announce_subnet_sale_into_lease_works() { new_test_ext(1).execute_with(|| { - // Setup a crowdloan let crowdloan_id = 0; + let creator = U256::from(1); + setup_crowdloan!(crowdloan_id, 0, 0, creator, vec![] as Vec<(U256, u64)>); + + let netuid = NetUid::from(1); + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, creator); + + let beneficiary = U256::from(2); + assert_ok!(SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::signed(creator), + netuid, + beneficiary, + )); + + let now = frame_system::Pallet::::block_number(); + assert_eq!( + SubnetSaleIntoLeaseAnnouncements::::iter().collect::>(), + vec![(creator, (now, beneficiary, netuid, crowdloan_id))] + ); + assert_last_event::(RuntimeEvent::SubtensorModule( + Event::SubnetSaleIntoLeaseAnnounced { + who: creator, + beneficiary, + netuid, + }, + )); + }); +} + +#[test] +fn test_announce_subnet_sale_into_lease_fails_if_bad_origin() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); let beneficiary = U256::from(1); - let deposit = 10_000_000_000; // 10 TAO - let cap = 1_000_000_000_000; // 1000 TAO - let contributions = vec![ - (U256::from(2), 600_000_000_000), // 600 TAO - (U256::from(3), 390_000_000_000), // 390 TAO - ]; - setup_crowdloan!(crowdloan_id, deposit, cap, beneficiary, &contributions); + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::none(), + netuid, + beneficiary, + ), + DispatchError::BadOrigin + ); + + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::root(), + netuid, + beneficiary, + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_announce_subnet_sale_into_lease_fails_if_coldkey_swap_announcement_exists() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + let creator = U256::from(1); + let beneficiary = U256::from(2); + let beneficiary_hash = ::Hashing::hash_of(&beneficiary); + let now = frame_system::Pallet::::block_number(); + setup_crowdloan!(0, 0, 0, creator, vec![] as Vec<(U256, u64)>); + + ColdkeySwapAnnouncements::::insert(creator, (now, beneficiary_hash)); + + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::signed(creator), + netuid, + beneficiary + ), + Error::::ColdkeySwapAnnouncementAlreadyExists + ); + }); +} + +#[test] +fn test_announce_subnet_sale_into_lease_fails_if_subnet_sale_into_lease_announcement_exists() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let netuid = NetUid::from(1); + let creator = U256::from(1); + let beneficiary = U256::from(2); + let now = frame_system::Pallet::::block_number(); + setup_crowdloan!(crowdloan_id, 0, 0, creator, vec![] as Vec<(U256, u64)>); + + SubnetSaleIntoLeaseAnnouncements::::insert( + creator, + (now, beneficiary, netuid, crowdloan_id), + ); + + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::signed(creator), + netuid, + beneficiary + ), + Error::::SubnetSaleIntoLeaseAnnouncementAlreadyExists + ); + }); +} + +#[test] +fn test_announce_subnet_sale_into_lease_fails_if_caller_doesnt_owns_subnet() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let creator = U256::from(1); + let beneficiary = U256::from(2); + setup_crowdloan!(crowdloan_id, 0, 0, creator, vec![] as Vec<(U256, u64)>); + + let netuid = NetUid::from(1); + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, U256::from(42)); + + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::signed(creator), + netuid, + beneficiary + ), + Error::::NotSubnetOwner + ); + }); +} + +#[test] +fn test_announce_subnet_sale_into_lease_fails_if_caller_owns_multiple_subnets() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let creator = U256::from(1); + let beneficiary = U256::from(2); + setup_crowdloan!(crowdloan_id, 0, 0, creator, vec![] as Vec<(U256, u64)>); + + let netuid1 = NetUid::from(1); + add_network(netuid1, 1, 0); + SubnetOwner::::insert(netuid1, creator); + let netuid2 = NetUid::from(2); + add_network(netuid2, 1, 0); + SubnetOwner::::insert(netuid2, creator); + + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::signed(creator), + netuid1, + beneficiary + ), + Error::::TooManySubnetsOwned + ); }); } From 8af7e5c375293a29f20d286ffd164dfce397164c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Dec 2025 15:16:34 +0100 Subject: [PATCH 51/62] swap coldkey with sale into lease announcement test --- pallets/subtensor/src/tests/swap_coldkey.rs | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index e9b6cf4c0b..69ca8b3651 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -198,6 +198,28 @@ fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() }); } +#[test] +fn test_announce_coldkey_swap_fails_if_subnet_sale_into_lease_announcement_exists() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + let new_coldkey = U256::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let now = System::block_number(); + let netuid = NetUid::from(1); + let crowdloan_id = 0; + + SubnetSaleIntoLeaseAnnouncements::::insert( + who, + (now, new_coldkey, netuid, crowdloan_id), + ); + + assert_noop!( + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_hash), + Error::::SubnetSaleIntoLeaseAnnouncementAlreadyExists + ); + }); +} + #[test] fn test_swap_coldkey_announced_works() { new_test_ext(1).execute_with(|| { From 34b0b08699cc496453c29b11b5af0ab68ef2c4b4 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 11 Dec 2025 15:25:42 +0100 Subject: [PATCH 52/62] added min sale price + checks --- pallets/subtensor/src/macros/dispatches.rs | 8 +++-- pallets/subtensor/src/macros/errors.rs | 2 ++ pallets/subtensor/src/subnets/leasing.rs | 2 +- pallets/subtensor/src/tests/leasing.rs | 36 ++++++++++++++++------ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index d3ace76ab2..f121408cda 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2461,12 +2461,16 @@ mod dispatches { origin: OriginFor, netuid: NetUid, beneficiary: T::AccountId, + min_sale_price: TaoCurrency, ) -> DispatchResult { let who = ensure_signed(origin)?; let now = frame_system::Pallet::::block_number(); - let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() - .ok_or(pallet_crowdloan::Error::::CurrentCrowdloanIdNotSet)?; + let (crowdloan_id, crowdloan) = Self::get_crowdloan_being_finalized()?; + ensure!( + crowdloan.raised >= min_sale_price.to_u64(), + Error::::SubnetMinSalePriceNotMet + ); ensure!( !ColdkeySwapAnnouncements::::contains_key(&who), diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index d5a854ba76..2a0b4b0d3b 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -276,5 +276,7 @@ mod errors { SubnetSaleIntoLeaseAnnouncementAlreadyExists, /// Too many subnets owned TooManySubnetsOwned, + /// Subnet minimum sale price not met. + SubnetMinSalePriceNotMet, } } diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index 12b7be7290..3db368aed3 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -377,7 +377,7 @@ impl Pallet { // Get the crowdloan being finalized from the crowdloan pallet when the call is executed, // and the current crowdloan ID is exposed to us. - fn get_crowdloan_being_finalized() -> Result< + pub(crate) fn get_crowdloan_being_finalized() -> Result< ( pallet_crowdloan::CrowdloanId, pallet_crowdloan::CrowdloanInfoOf, diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index f76b60a44c..82d126330d 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -1040,7 +1040,13 @@ fn test_announce_subnet_sale_into_lease_works() { new_test_ext(1).execute_with(|| { let crowdloan_id = 0; let creator = U256::from(1); - setup_crowdloan!(crowdloan_id, 0, 0, creator, vec![] as Vec<(U256, u64)>); + setup_crowdloan!( + crowdloan_id, + 0, + 100_000_000, + creator, + vec![] as Vec<(U256, u64)> + ); let netuid = NetUid::from(1); add_network(netuid, 1, 0); @@ -1051,6 +1057,7 @@ fn test_announce_subnet_sale_into_lease_works() { RuntimeOrigin::signed(creator), netuid, beneficiary, + TaoCurrency::from(100_000_000), )); let now = frame_system::Pallet::::block_number(); @@ -1073,12 +1080,14 @@ fn test_announce_subnet_sale_into_lease_fails_if_bad_origin() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); let beneficiary = U256::from(1); + let min_sale_price = TaoCurrency::from(100_000_000); assert_noop!( SubtensorModule::announce_subnet_sale_into_lease( RuntimeOrigin::none(), netuid, beneficiary, + min_sale_price, ), DispatchError::BadOrigin ); @@ -1088,6 +1097,7 @@ fn test_announce_subnet_sale_into_lease_fails_if_bad_origin() { RuntimeOrigin::root(), netuid, beneficiary, + min_sale_price, ), DispatchError::BadOrigin ); @@ -1102,7 +1112,8 @@ fn test_announce_subnet_sale_into_lease_fails_if_coldkey_swap_announcement_exist let beneficiary = U256::from(2); let beneficiary_hash = ::Hashing::hash_of(&beneficiary); let now = frame_system::Pallet::::block_number(); - setup_crowdloan!(0, 0, 0, creator, vec![] as Vec<(U256, u64)>); + let min_sale_price = TaoCurrency::from(100_000_000); + setup_crowdloan!(0, 0, min_sale_price.to_u64(), creator, vec![] as Vec<(U256, u64)>); ColdkeySwapAnnouncements::::insert(creator, (now, beneficiary_hash)); @@ -1110,7 +1121,8 @@ fn test_announce_subnet_sale_into_lease_fails_if_coldkey_swap_announcement_exist SubtensorModule::announce_subnet_sale_into_lease( RuntimeOrigin::signed(creator), netuid, - beneficiary + beneficiary, + min_sale_price, ), Error::::ColdkeySwapAnnouncementAlreadyExists ); @@ -1125,7 +1137,8 @@ fn test_announce_subnet_sale_into_lease_fails_if_subnet_sale_into_lease_announce let creator = U256::from(1); let beneficiary = U256::from(2); let now = frame_system::Pallet::::block_number(); - setup_crowdloan!(crowdloan_id, 0, 0, creator, vec![] as Vec<(U256, u64)>); + let min_sale_price = TaoCurrency::from(100_000_000); + setup_crowdloan!(crowdloan_id, 0, min_sale_price.to_u64(), creator, vec![] as Vec<(U256, u64)>); SubnetSaleIntoLeaseAnnouncements::::insert( creator, @@ -1136,7 +1149,8 @@ fn test_announce_subnet_sale_into_lease_fails_if_subnet_sale_into_lease_announce SubtensorModule::announce_subnet_sale_into_lease( RuntimeOrigin::signed(creator), netuid, - beneficiary + beneficiary, + min_sale_price, ), Error::::SubnetSaleIntoLeaseAnnouncementAlreadyExists ); @@ -1149,7 +1163,8 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_doesnt_owns_subnet() { let crowdloan_id = 0; let creator = U256::from(1); let beneficiary = U256::from(2); - setup_crowdloan!(crowdloan_id, 0, 0, creator, vec![] as Vec<(U256, u64)>); + let min_sale_price = TaoCurrency::from(100_000_000); + setup_crowdloan!(crowdloan_id, 0, min_sale_price.to_u64(), creator, vec![] as Vec<(U256, u64)>); let netuid = NetUid::from(1); add_network(netuid, 1, 0); @@ -1159,7 +1174,8 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_doesnt_owns_subnet() { SubtensorModule::announce_subnet_sale_into_lease( RuntimeOrigin::signed(creator), netuid, - beneficiary + beneficiary, + min_sale_price, ), Error::::NotSubnetOwner ); @@ -1172,7 +1188,8 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_owns_multiple_subnets() let crowdloan_id = 0; let creator = U256::from(1); let beneficiary = U256::from(2); - setup_crowdloan!(crowdloan_id, 0, 0, creator, vec![] as Vec<(U256, u64)>); + let min_sale_price = TaoCurrency::from(100_000_000); + setup_crowdloan!(crowdloan_id, 0, min_sale_price.to_u64(), creator, vec![] as Vec<(U256, u64)>); let netuid1 = NetUid::from(1); add_network(netuid1, 1, 0); @@ -1185,7 +1202,8 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_owns_multiple_subnets() SubtensorModule::announce_subnet_sale_into_lease( RuntimeOrigin::signed(creator), netuid1, - beneficiary + beneficiary, + min_sale_price, ), Error::::TooManySubnetsOwned ); From e9770cecded5987ce432a8a257671323b25cf1d7 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 11 Dec 2025 15:42:28 +0100 Subject: [PATCH 53/62] fix error --- pallets/subtensor/src/subnets/leasing.rs | 2 +- pallets/subtensor/src/tests/leasing.rs | 34 ++++++++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index 3db368aed3..c53e0672fb 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -77,7 +77,7 @@ impl Pallet { let (crowdloan_id, crowdloan) = Self::get_crowdloan_being_finalized()?; ensure!( who == crowdloan.creator, - Error::::InvalidLeaseBeneficiary + Error::::ExpectedBeneficiaryOrigin ); if let Some(end_block) = end_block { diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 82d126330d..a1e376a08b 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -203,7 +203,7 @@ fn test_register_leased_network_fails_if_origin_is_not_crowdloan_creator() { emissions_share, Some(end_block), ), - Error::::InvalidLeaseBeneficiary, + Error::::ExpectedBeneficiaryOrigin, ); }); } @@ -1113,7 +1113,13 @@ fn test_announce_subnet_sale_into_lease_fails_if_coldkey_swap_announcement_exist let beneficiary_hash = ::Hashing::hash_of(&beneficiary); let now = frame_system::Pallet::::block_number(); let min_sale_price = TaoCurrency::from(100_000_000); - setup_crowdloan!(0, 0, min_sale_price.to_u64(), creator, vec![] as Vec<(U256, u64)>); + setup_crowdloan!( + 0, + 0, + min_sale_price.to_u64(), + creator, + vec![] as Vec<(U256, u64)> + ); ColdkeySwapAnnouncements::::insert(creator, (now, beneficiary_hash)); @@ -1138,7 +1144,13 @@ fn test_announce_subnet_sale_into_lease_fails_if_subnet_sale_into_lease_announce let beneficiary = U256::from(2); let now = frame_system::Pallet::::block_number(); let min_sale_price = TaoCurrency::from(100_000_000); - setup_crowdloan!(crowdloan_id, 0, min_sale_price.to_u64(), creator, vec![] as Vec<(U256, u64)>); + setup_crowdloan!( + crowdloan_id, + 0, + min_sale_price.to_u64(), + creator, + vec![] as Vec<(U256, u64)> + ); SubnetSaleIntoLeaseAnnouncements::::insert( creator, @@ -1164,7 +1176,13 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_doesnt_owns_subnet() { let creator = U256::from(1); let beneficiary = U256::from(2); let min_sale_price = TaoCurrency::from(100_000_000); - setup_crowdloan!(crowdloan_id, 0, min_sale_price.to_u64(), creator, vec![] as Vec<(U256, u64)>); + setup_crowdloan!( + crowdloan_id, + 0, + min_sale_price.to_u64(), + creator, + vec![] as Vec<(U256, u64)> + ); let netuid = NetUid::from(1); add_network(netuid, 1, 0); @@ -1189,7 +1207,13 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_owns_multiple_subnets() let creator = U256::from(1); let beneficiary = U256::from(2); let min_sale_price = TaoCurrency::from(100_000_000); - setup_crowdloan!(crowdloan_id, 0, min_sale_price.to_u64(), creator, vec![] as Vec<(U256, u64)>); + setup_crowdloan!( + crowdloan_id, + 0, + min_sale_price.to_u64(), + creator, + vec![] as Vec<(U256, u64)> + ); let netuid1 = NetUid::from(1); add_network(netuid1, 1, 0); From e600ba050b1879d0f1148f063fb7ac0b1b258efd Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 11 Dec 2025 19:14:09 +0100 Subject: [PATCH 54/62] wip --- pallets/subtensor/src/macros/dispatches.rs | 4 +- pallets/subtensor/src/macros/errors.rs | 6 ++ pallets/subtensor/src/subnets/leasing.rs | 88 +++++++++++++++++++++- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index f121408cda..881397f842 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2507,8 +2507,8 @@ mod dispatches { /// Settles a subnet sale into a lease. #[pallet::call_index(129)] #[pallet::weight(Weight::zero())] - pub fn settle_subnet_sale_into_lease(_origin: OriginFor) -> DispatchResult { - Ok(()) + pub fn settle_subnet_sale_into_lease(origin: OriginFor) -> DispatchResult { + Self::do_settle_subnet_sale_into_lease(origin) } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 2a0b4b0d3b..c10462ca70 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -278,5 +278,11 @@ mod errors { TooManySubnetsOwned, /// Subnet minimum sale price not met. SubnetMinSalePriceNotMet, + /// Subnet sale into lease announcement not found. + SubnetSaleIntoLeaseAnnouncementNotFound, + /// Subnet sale into lease announcement settled too early. + SubnetLeaseIntoSaleSettledTooEarly, + /// Expected subnet seller origin. + ExpectedSubnetSellerOrigin, } } diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index c53e0672fb..93c8f9aa7c 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -22,7 +22,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use sp_core::blake2_256; -use sp_runtime::{Percent, traits::TrailingZeroInput}; +use sp_runtime::{Percent, Saturating, traits::TrailingZeroInput}; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, NetUid}; @@ -188,6 +188,92 @@ impl Pallet { } } + /// Settle a subnet sale into a lease. + pub fn do_settle_subnet_sale_into_lease(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + + let (when, beneficiary, netuid, crowdloan_id) = + SubnetSaleIntoLeaseAnnouncements::::take(who.clone()) + .ok_or(Error::::SubnetSaleIntoLeaseAnnouncementNotFound)?; + + let now = >::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); + ensure!( + now > when.saturating_add(delay), + Error::::SubnetLeaseIntoSaleSettledTooEarly + ); + + let crowdloan = pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .ok_or(pallet_crowdloan::Error::::InvalidCrowdloanId)?; + + let lease_id = Self::get_next_lease_id()?; + let lease_coldkey = Self::lease_coldkey(lease_id)?; + let lease_hotkey = Self::lease_hotkey(lease_id)?; + frame_system::Pallet::::inc_providers(&lease_coldkey); + frame_system::Pallet::::inc_providers(&lease_hotkey); + + ::Currency::transfer( + &crowdloan.funds_account, + &lease_coldkey, + crowdloan.raised, + Preservation::Expendable, + )?; + + // Swap the coldkey to the lease coldkey + Self::do_swap_coldkey(&who, &lease_coldkey, TaoCurrency::ZERO)?; + + // Enable the beneficiary to operate the subnet through a proxy + T::ProxyInterface::add_lease_beneficiary_proxy(&lease_coldkey, &beneficiary)?; + + // Get left leftover cap and compute the cost of the registration + proxy + let leftover_cap = ::Currency::balance(&lease_coldkey); + let cost = crowdloan.raised.saturating_sub(leftover_cap); + + SubnetLeases::::insert( + lease_id, + SubnetLease { + beneficiary: beneficiary.clone(), + coldkey: lease_coldkey.clone(), + hotkey: lease_hotkey.clone(), + emissions_share: Percent::from_percent(100), + end_block: None, + netuid, + cost, + }, + ); + SubnetUidToLeaseId::::insert(netuid, lease_id); + + // The lease take should be 0% to allow all contributors to receive dividends entirely. + Self::delegate_hotkey(&lease_hotkey, 0); + + // Get all the contributions to the crowdloan except for the beneficiary + // because its share will be computed as the dividends are distributed + let contributions = pallet_crowdloan::Contributions::::iter_prefix(crowdloan_id) + .into_iter() + .filter(|(contributor, _)| contributor != &beneficiary); + + for (contributor, amount) in contributions { + // Compute the share of the contributor to the lease + let share: U64F64 = U64F64::from(amount).saturating_div(U64F64::from(crowdloan.raised)); + SubnetLeaseShares::::insert(lease_id, &contributor, share); + } + + ::Currency::transfer( + &lease_coldkey, + &who, + leftover_cap, + Preservation::Expendable, + )?; + + Self::deposit_event(Event::SubnetLeaseCreated { + beneficiary, + lease_id, + netuid, + end_block: None, + }); + Ok(()) + } + /// Terminate a lease. /// /// The beneficiary can terminate the lease after the end block has passed and get the subnet ownership. From e6beb7193611a4e27346cc5566d71ecd8d5413dc Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 12 Dec 2025 15:14:43 +0100 Subject: [PATCH 55/62] rework common logic --- pallets/subtensor/src/subnets/leasing.rs | 199 +++++++++++------------ 1 file changed, 97 insertions(+), 102 deletions(-) diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index 93c8f9aa7c..083b3ae5b8 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -33,8 +33,8 @@ pub type CurrencyOf = ::Currency; pub type BalanceOf = as fungible::Inspect<::AccountId>>::Balance; -#[freeze_struct("8cc3d0594faed7dd")] -#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo)] +#[freeze_struct("eec95dad4d06b2d5")] +#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, Clone)] pub struct SubnetLease { /// The beneficiary of the lease, able to operate the subnet through /// a proxy and taking ownership of the subnet at the end of the lease (if defined). @@ -84,53 +84,16 @@ impl Pallet { ensure!(end_block > now, Error::::LeaseCannotEndInThePast); } - // Initialize the lease id, coldkey and hotkey and keep track of them - let lease_id = Self::get_next_lease_id()?; - let lease_coldkey = Self::lease_coldkey(lease_id)?; - let lease_hotkey = Self::lease_hotkey(lease_id)?; - frame_system::Pallet::::inc_providers(&lease_coldkey); - frame_system::Pallet::::inc_providers(&lease_hotkey); - - ::Currency::transfer( - &crowdloan.funds_account, - &lease_coldkey, - crowdloan.raised, - Preservation::Expendable, - )?; - - Self::do_register_network( - RawOrigin::Signed(lease_coldkey.clone()).into(), - &lease_hotkey, - 1, - None, - )?; - - let netuid = - Self::find_lease_netuid(&lease_coldkey).ok_or(Error::::LeaseNetuidNotFound)?; + let (lease_id, lease) = + Self::initialize_lease(&crowdloan, &who, emissions_share, end_block)?; - // Enable the beneficiary to operate the subnet through a proxy - T::ProxyInterface::add_lease_beneficiary_proxy(&lease_coldkey, &who)?; + let origin = RawOrigin::Signed(lease.coldkey.clone()).into(); + let mechid = 1; + Self::do_register_network(origin, &lease.hotkey, mechid, None)?; - // Get left leftover cap and compute the cost of the registration + proxy - let leftover_cap = ::Currency::balance(&lease_coldkey); - let cost = crowdloan.raised.saturating_sub(leftover_cap); - - SubnetLeases::::insert( - lease_id, - SubnetLease { - beneficiary: who.clone(), - coldkey: lease_coldkey.clone(), - hotkey: lease_hotkey.clone(), - emissions_share, - end_block, - netuid, - cost, - }, - ); - SubnetUidToLeaseId::::insert(netuid, lease_id); - - // The lease take should be 0% to allow all contributors to receive dividends entirely. - Self::delegate_hotkey(&lease_hotkey, 0); + let netuid = Self::find_lease_netuid(&lease.coldkey)?; + let leftover_cap = + Self::finalize_lease_creation(&crowdloan, lease_id, lease.clone(), netuid)?; // Get all the contributions to the crowdloan except for the beneficiary // because its share will be computed as the dividends are distributed @@ -150,7 +113,7 @@ impl Pallet { .floor() .saturating_to_num::(); ::Currency::transfer( - &lease_coldkey, + &lease.coldkey, &contributor, contributor_refund, Preservation::Expendable, @@ -161,7 +124,7 @@ impl Pallet { // Refund what's left after refunding the contributors to the beneficiary let beneficiary_refund = leftover_cap.saturating_sub(refunded_cap); ::Currency::transfer( - &lease_coldkey, + &lease.coldkey, &who, beneficiary_refund, Preservation::Expendable, @@ -206,45 +169,15 @@ impl Pallet { let crowdloan = pallet_crowdloan::Crowdloans::::get(crowdloan_id) .ok_or(pallet_crowdloan::Error::::InvalidCrowdloanId)?; - let lease_id = Self::get_next_lease_id()?; - let lease_coldkey = Self::lease_coldkey(lease_id)?; - let lease_hotkey = Self::lease_hotkey(lease_id)?; - frame_system::Pallet::::inc_providers(&lease_coldkey); - frame_system::Pallet::::inc_providers(&lease_hotkey); - - ::Currency::transfer( - &crowdloan.funds_account, - &lease_coldkey, - crowdloan.raised, - Preservation::Expendable, - )?; - - // Swap the coldkey to the lease coldkey - Self::do_swap_coldkey(&who, &lease_coldkey, TaoCurrency::ZERO)?; - - // Enable the beneficiary to operate the subnet through a proxy - T::ProxyInterface::add_lease_beneficiary_proxy(&lease_coldkey, &beneficiary)?; - - // Get left leftover cap and compute the cost of the registration + proxy - let leftover_cap = ::Currency::balance(&lease_coldkey); - let cost = crowdloan.raised.saturating_sub(leftover_cap); + let emissions_share = Percent::from_percent(100); + let end_block = None; + let (lease_id, lease) = + Self::initialize_lease(&crowdloan, &beneficiary, emissions_share, end_block)?; - SubnetLeases::::insert( - lease_id, - SubnetLease { - beneficiary: beneficiary.clone(), - coldkey: lease_coldkey.clone(), - hotkey: lease_hotkey.clone(), - emissions_share: Percent::from_percent(100), - end_block: None, - netuid, - cost, - }, - ); - SubnetUidToLeaseId::::insert(netuid, lease_id); + Self::do_swap_coldkey(&who, &lease.coldkey, TaoCurrency::ZERO)?; - // The lease take should be 0% to allow all contributors to receive dividends entirely. - Self::delegate_hotkey(&lease_hotkey, 0); + let leftover_cap = + Self::finalize_lease_creation(&crowdloan, lease_id, lease.clone(), netuid)?; // Get all the contributions to the crowdloan except for the beneficiary // because its share will be computed as the dividends are distributed @@ -259,7 +192,7 @@ impl Pallet { } ::Currency::transfer( - &lease_coldkey, + &lease.coldkey, &who, leftover_cap, Preservation::Expendable, @@ -433,6 +366,22 @@ impl Pallet { }; } + // Get the crowdloan being finalized from the crowdloan pallet when the call is executed, + // and the current crowdloan ID is exposed to us. + pub(crate) fn get_crowdloan_being_finalized() -> Result< + ( + pallet_crowdloan::CrowdloanId, + pallet_crowdloan::CrowdloanInfoOf, + ), + pallet_crowdloan::Error, + > { + let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() + .ok_or(pallet_crowdloan::Error::::CurrentCrowdloanIdNotSet)?; + let crowdloan = pallet_crowdloan::Crowdloans::::get(crowdloan_id) + .ok_or(pallet_crowdloan::Error::::InvalidCrowdloanId)?; + Ok((crowdloan_id, crowdloan)) + } + fn lease_coldkey(lease_id: LeaseId) -> Result { let entropy = ("leasing/coldkey", lease_id).using_encoded(blake2_256); T::AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) @@ -455,26 +404,72 @@ impl Pallet { Ok(lease_id) } - fn find_lease_netuid(lease_coldkey: &T::AccountId) -> Option { + fn find_lease_netuid(lease_coldkey: &T::AccountId) -> Result> { SubnetOwner::::iter() .find(|(_, coldkey)| coldkey == lease_coldkey) .map(|(netuid, _)| netuid) + .ok_or(Error::::LeaseNetuidNotFound) } - // Get the crowdloan being finalized from the crowdloan pallet when the call is executed, - // and the current crowdloan ID is exposed to us. - pub(crate) fn get_crowdloan_being_finalized() -> Result< - ( - pallet_crowdloan::CrowdloanId, - pallet_crowdloan::CrowdloanInfoOf, - ), - pallet_crowdloan::Error, - > { - let crowdloan_id = pallet_crowdloan::CurrentCrowdloanId::::get() - .ok_or(pallet_crowdloan::Error::::CurrentCrowdloanIdNotSet)?; - let crowdloan = pallet_crowdloan::Crowdloans::::get(crowdloan_id) - .ok_or(pallet_crowdloan::Error::::InvalidCrowdloanId)?; - Ok((crowdloan_id, crowdloan)) + fn initialize_lease( + crowdloan: &pallet_crowdloan::CrowdloanInfoOf, + beneficiary: &T::AccountId, + emissions_share: Percent, + end_block: Option>, + ) -> Result<(LeaseId, SubnetLeaseOf), DispatchError> { + let lease_id = Self::get_next_lease_id()?; + let lease_coldkey = Self::lease_coldkey(lease_id)?; + let lease_hotkey = Self::lease_hotkey(lease_id)?; + frame_system::Pallet::::inc_providers(&lease_coldkey); + frame_system::Pallet::::inc_providers(&lease_hotkey); + + ::Currency::transfer( + &crowdloan.funds_account, + &lease_coldkey, + crowdloan.raised, + Preservation::Expendable, + )?; + + let lease = SubnetLease { + coldkey: lease_coldkey, + hotkey: lease_hotkey, + beneficiary: beneficiary.clone(), + emissions_share, + end_block, + netuid: Default::default(), + cost: Default::default(), + }; + + Ok((lease_id, lease)) + } + + fn finalize_lease_creation( + crowdloan: &pallet_crowdloan::CrowdloanInfoOf, + lease_id: LeaseId, + lease: SubnetLeaseOf, + netuid: NetUid, + ) -> Result, DispatchError> { + // Enable the beneficiary to operate the subnet through a proxy + T::ProxyInterface::add_lease_beneficiary_proxy(&lease.coldkey, &lease.beneficiary)?; + + // Get left leftover cap and compute the overall cost of the lease + let leftover_cap = ::Currency::balance(&lease.coldkey); + let cost = crowdloan.raised.saturating_sub(leftover_cap); + + // The lease take should be 0% to allow all contributors to receive dividends entirely + Self::delegate_hotkey(&lease.hotkey, 0); + + SubnetLeases::::insert( + lease_id, + SubnetLease { + netuid, + cost, + ..lease + }, + ); + SubnetUidToLeaseId::::insert(netuid, lease_id); + + Ok(leftover_cap) } } From 7713a18de5330bbe074710f032087e2916ad654e Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 12 Dec 2025 15:35:57 +0100 Subject: [PATCH 56/62] reorg --- pallets/subtensor/src/macros/dispatches.rs | 40 +------------- pallets/subtensor/src/subnets/leasing.rs | 63 +++++++++++++++++++++- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 881397f842..3c7cdc3a44 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2463,45 +2463,7 @@ mod dispatches { beneficiary: T::AccountId, min_sale_price: TaoCurrency, ) -> DispatchResult { - let who = ensure_signed(origin)?; - let now = frame_system::Pallet::::block_number(); - - let (crowdloan_id, crowdloan) = Self::get_crowdloan_being_finalized()?; - ensure!( - crowdloan.raised >= min_sale_price.to_u64(), - Error::::SubnetMinSalePriceNotMet - ); - - ensure!( - !ColdkeySwapAnnouncements::::contains_key(&who), - Error::::ColdkeySwapAnnouncementAlreadyExists - ); - ensure!( - !SubnetSaleIntoLeaseAnnouncements::::contains_key(&who), - Error::::SubnetSaleIntoLeaseAnnouncementAlreadyExists - ); - - ensure!( - SubnetOwner::::get(netuid) == who, - Error::::NotSubnetOwner - ); - let owners = SubnetOwner::::iter().collect::>(); - ensure!( - owners.iter().filter(|(_, owner)| owner == &who).count() == 1, - Error::::TooManySubnetsOwned - ); - - SubnetSaleIntoLeaseAnnouncements::::insert( - &who, - (now, beneficiary.clone(), netuid, crowdloan_id), - ); - - Self::deposit_event(Event::SubnetSaleIntoLeaseAnnounced { - who, - beneficiary, - netuid, - }); - Ok(()) + Self::do_announce_subnet_sale_into_lease(origin, netuid, beneficiary, min_sale_price) } /// Settles a subnet sale into a lease. diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index 083b3ae5b8..99a4206d9a 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -151,7 +151,68 @@ impl Pallet { } } - /// Settle a subnet sale into a lease. + /// Announces a subnet sale into a lease using the beneficiary coldkey and the min sale price. + /// + /// The caller must be the subnet owner and the crowdloan's raised amount must be greater + /// than or equal to the min sale price. + pub fn do_announce_subnet_sale_into_lease( + origin: OriginFor, + netuid: NetUid, + beneficiary: T::AccountId, + min_sale_price: TaoCurrency, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + + let (crowdloan_id, crowdloan) = Self::get_crowdloan_being_finalized()?; + ensure!( + crowdloan.raised >= min_sale_price.to_u64(), + Error::::SubnetMinSalePriceNotMet + ); + + ensure!( + !ColdkeySwapAnnouncements::::contains_key(&who), + Error::::ColdkeySwapAnnouncementAlreadyExists + ); + ensure!( + !SubnetSaleIntoLeaseAnnouncements::::contains_key(&who), + Error::::SubnetSaleIntoLeaseAnnouncementAlreadyExists + ); + + ensure!( + SubnetOwner::::get(netuid) == who, + Error::::NotSubnetOwner + ); + let owners = SubnetOwner::::iter().collect::>(); + ensure!( + owners.iter().filter(|(_, owner)| owner == &who).count() == 1, + Error::::TooManySubnetsOwned + ); + + SubnetSaleIntoLeaseAnnouncements::::insert( + &who, + (now, beneficiary.clone(), netuid, crowdloan_id), + ); + + Self::deposit_event(Event::SubnetSaleIntoLeaseAnnounced { + who, + beneficiary, + netuid, + }); + Ok(()) + } + + /// Settles a subnet sale into a lease. + /// + /// The caller must be the subnet owner and the announcement must be older than the coldkey swap announcement delay. + /// + /// The coldkey swap will`` be performed and a new lease will be created with the beneficiary coldkey + /// to operate the subnet through a proxy. + /// + /// The crowdloan's contributions are used to compute the share of the emissions that the contributors + /// will receive as dividends. + /// + /// The leftover cap is paid to the seller. pub fn do_settle_subnet_sale_into_lease(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; From c011adead8df4deaf3ec602dcad9b3ae42cdaa20 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sat, 13 Dec 2025 15:54:37 +0100 Subject: [PATCH 57/62] cancel subnet sale into lease + events --- pallets/subtensor/src/macros/dispatches.rs | 10 +++++ pallets/subtensor/src/macros/events.rs | 18 ++++++++- pallets/subtensor/src/subnets/leasing.rs | 43 +++++++++++++++++----- pallets/subtensor/src/tests/leasing.rs | 5 ++- 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 3c7cdc3a44..60bc73d358 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2472,5 +2472,15 @@ mod dispatches { pub fn settle_subnet_sale_into_lease(origin: OriginFor) -> DispatchResult { Self::do_settle_subnet_sale_into_lease(origin) } + + /// Cancels a subnet sale into a lease. + /// + /// The crowdloan will be unmarked as being finalized which will allow + /// the contributions to be refunded. + #[pallet::call_index(130)] + #[pallet::weight(Weight::zero())] + pub fn cancel_subnet_sale_into_lease(origin: OriginFor) -> DispatchResult { + Self::do_cancel_subnet_sale_into_lease(origin) + } } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 764e2d8430..3a98158ecb 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -483,12 +483,26 @@ mod events { /// A subnet sale into lease announcement has been made. SubnetSaleIntoLeaseAnnounced { - /// The account ID of the coldkey that made the announcement. - who: T::AccountId, /// The account ID of the beneficiary. beneficiary: T::AccountId, /// The network identifier. netuid: NetUid, + /// The minimum sale price. + min_sale_price: TaoCurrency, + }, + + /// A subnet sale into lease has been settled. + SubnetSaleIntoLeaseSettled { + /// The account ID of the beneficiary. + beneficiary: T::AccountId, + /// The network identifier. + netuid: NetUid, + }, + + /// A subnet sale into lease announcement has been cancelled. + SubnetSaleIntoLeaseCancelled { + /// The network identifier. + netuid: NetUid, }, } } diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index 99a4206d9a..a4a1512f40 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -152,8 +152,8 @@ impl Pallet { } /// Announces a subnet sale into a lease using the beneficiary coldkey and the min sale price. - /// - /// The caller must be the subnet owner and the crowdloan's raised amount must be greater + /// + /// The caller must be the subnet owner and the crowdloan's raised amount must be greater /// than or equal to the min sale price. pub fn do_announce_subnet_sale_into_lease( origin: OriginFor, @@ -190,23 +190,23 @@ impl Pallet { ); SubnetSaleIntoLeaseAnnouncements::::insert( - &who, + who, (now, beneficiary.clone(), netuid, crowdloan_id), ); Self::deposit_event(Event::SubnetSaleIntoLeaseAnnounced { - who, beneficiary, netuid, + min_sale_price, }); Ok(()) } /// Settles a subnet sale into a lease. - /// + /// /// The caller must be the subnet owner and the announcement must be older than the coldkey swap announcement delay. - /// - /// The coldkey swap will`` be performed and a new lease will be created with the beneficiary coldkey + /// + /// The coldkey swap will`` be performed and a new lease will be created with the beneficiary coldkey /// to operate the subnet through a proxy. /// /// The crowdloan's contributions are used to compute the share of the emissions that the contributors @@ -235,7 +235,7 @@ impl Pallet { let (lease_id, lease) = Self::initialize_lease(&crowdloan, &beneficiary, emissions_share, end_block)?; - Self::do_swap_coldkey(&who, &lease.coldkey, TaoCurrency::ZERO)?; + Self::do_swap_coldkey(&who, &lease.coldkey)?; let leftover_cap = Self::finalize_lease_creation(&crowdloan, lease_id, lease.clone(), netuid)?; @@ -260,14 +260,37 @@ impl Pallet { )?; Self::deposit_event(Event::SubnetLeaseCreated { - beneficiary, + beneficiary: who, lease_id, netuid, - end_block: None, + end_block, + }); + Self::deposit_event(Event::SubnetSaleIntoLeaseSettled { + beneficiary, + netuid, }); Ok(()) } + pub fn do_cancel_subnet_sale_into_lease(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + + let (_, _, netuid, crowdloan_id) = SubnetSaleIntoLeaseAnnouncements::::take(who.clone()) + .ok_or(Error::::SubnetSaleIntoLeaseAnnouncementNotFound)?; + + pallet_crowdloan::Crowdloans::::try_mutate(crowdloan_id, |crowdloan| match crowdloan { + Some(crowdloan) => { + // Unmark the crowdloan as finalized to allow the contributions to be refunded + crowdloan.finalized = false; + Ok::<_, DispatchError>(()) + } + None => Err(pallet_crowdloan::Error::::InvalidCrowdloanId.into()), + })?; + + Self::deposit_event(Event::SubnetSaleIntoLeaseCancelled { netuid }); + Ok(()) + } + /// Terminate a lease. /// /// The beneficiary can terminate the lease after the end block has passed and get the subnet ownership. diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index a1e376a08b..38ea516404 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -1053,11 +1053,12 @@ fn test_announce_subnet_sale_into_lease_works() { SubnetOwner::::insert(netuid, creator); let beneficiary = U256::from(2); + let min_sale_price = TaoCurrency::from(100_000_000); assert_ok!(SubtensorModule::announce_subnet_sale_into_lease( RuntimeOrigin::signed(creator), netuid, beneficiary, - TaoCurrency::from(100_000_000), + min_sale_price.clone(), )); let now = frame_system::Pallet::::block_number(); @@ -1067,9 +1068,9 @@ fn test_announce_subnet_sale_into_lease_works() { ); assert_last_event::(RuntimeEvent::SubtensorModule( Event::SubnetSaleIntoLeaseAnnounced { - who: creator, beneficiary, netuid, + min_sale_price, }, )); }); From d8e0adbb6a7157eef0ca471b5594f48ae7afe879 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Sat, 13 Dec 2025 15:57:49 +0100 Subject: [PATCH 58/62] cargo clippy --- pallets/subtensor/src/tests/leasing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 38ea516404..d1cba4b1df 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -1058,7 +1058,7 @@ fn test_announce_subnet_sale_into_lease_works() { RuntimeOrigin::signed(creator), netuid, beneficiary, - min_sale_price.clone(), + min_sale_price, )); let now = frame_system::Pallet::::block_number(); From 0385eaff5d25d8d88532668bcaae702eed75e817 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 15 Dec 2025 10:38:46 +0100 Subject: [PATCH 59/62] cancel announcement tests --- pallets/subtensor/src/subnets/leasing.rs | 6 +- pallets/subtensor/src/tests/leasing.rs | 136 ++++++++++++++++++++++- 2 files changed, 138 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index a4a1512f40..09070849c1 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -240,11 +240,11 @@ impl Pallet { let leftover_cap = Self::finalize_lease_creation(&crowdloan, lease_id, lease.clone(), netuid)?; - // Get all the contributions to the crowdloan except for the beneficiary - // because its share will be computed as the dividends are distributed + // Get all the contributions to the crowdloan except the seller because he doesn't get any shares + // and the beneficiary because its share will be computed as the dividends are distributed let contributions = pallet_crowdloan::Contributions::::iter_prefix(crowdloan_id) .into_iter() - .filter(|(contributor, _)| contributor != &beneficiary); + .filter(|(contributor, _)| contributor != &who && contributor != &beneficiary); for (contributor, amount) in contributions { // Compute the share of the contributor to the lease diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index d1cba4b1df..2934a05b79 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -1235,6 +1235,140 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_owns_multiple_subnets() }); } +#[test] +fn test_cancel_subnet_sale_into_lease_works() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let deposit = 10_000_000_000; + let creator = U256::from(1); + let beneficiary = U256::from(2); + let now = frame_system::Pallet::::block_number(); + let min_sale_price = TaoCurrency::from(100_000_000_000); + setup_crowdloan!( + crowdloan_id, + deposit, + min_sale_price.to_u64(), + creator, + vec![ + (beneficiary, 80_000_000_000u64), + (U256::from(3), 100_000_000) + ] + ); + + let netuid = NetUid::from(1); + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, creator); + + SubnetSaleIntoLeaseAnnouncements::::insert( + creator, + (now, beneficiary, netuid, crowdloan_id), + ); + pallet_crowdloan::Crowdloans::::mutate(crowdloan_id, |crowdloan| { + if let Some(crowdloan) = crowdloan { + crowdloan.finalized = true; + } + }); + + // We can't refund a finalized crowdloan + assert_noop!( + pallet_crowdloan::Pallet::::refund(RuntimeOrigin::signed(creator), crowdloan_id), + pallet_crowdloan::Error::::AlreadyFinalized + ); + + assert_ok!(SubtensorModule::cancel_subnet_sale_into_lease( + RuntimeOrigin::signed(creator), + )); + + assert_eq!( + SubnetSaleIntoLeaseAnnouncements::::iter().collect::>(), + vec![] + ); + assert_last_event::(RuntimeEvent::SubtensorModule( + Event::SubnetSaleIntoLeaseCancelled { netuid }, + )); + + // We can emit refunds for the crowdloan + assert_ok!(pallet_crowdloan::Pallet::::refund( + RuntimeOrigin::signed(creator), + crowdloan_id + )); + }); +} + +#[test] +fn test_cancel_subnet_sale_into_lease_fails_if_bad_origin() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let creator = U256::from(1); + setup_crowdloan!( + crowdloan_id, + 0, + 100_000_000, + creator, + vec![] as Vec<(U256, u64)> + ); + + assert_noop!( + SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::none()), + DispatchError::BadOrigin + ); + + assert_noop!( + SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::root()), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_cancel_subnet_sale_into_lease_fails_if_no_subnet_sale_into_lease_announcement_exists() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let creator = U256::from(1); + setup_crowdloan!( + crowdloan_id, + 0, + 100_000_000, + creator, + vec![] as Vec<(U256, u64)> + ); + + assert_noop!( + SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::signed(creator)), + Error::::SubnetSaleIntoLeaseAnnouncementNotFound + ); + }); +} + +#[test] +fn test_cancel_subnet_sale_into_lease_fails_if_crowdloan_does_not_exists() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let creator = U256::from(1); + let beneficiary = U256::from(2); + let now = frame_system::Pallet::::block_number(); + let netuid = NetUid::from(1); + setup_crowdloan!( + crowdloan_id, + 0, + 100_000_000, + creator, + vec![] as Vec<(U256, u64)> + ); + + SubnetSaleIntoLeaseAnnouncements::::insert( + creator, + // Bogus crowdloan id + (now, beneficiary, netuid, crowdloan_id + 1), + ); + + assert_noop!( + SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::signed(creator)), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + #[macro_export] macro_rules! setup_crowdloan { ($id:expr, $deposit:expr, $cap:expr, $creator:expr, $contributions:expr) => { @@ -1245,7 +1379,7 @@ macro_rules! setup_crowdloan { pallet_crowdloan::CrowdloanInfo { creator: $creator, deposit: $deposit, - min_contribution: 0, + min_contribution: 100_000_000, end: 0, cap: $cap, raised: $cap, From 00205fdb18574052962fa3777123800cd858b5cb Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 15 Dec 2025 15:26:25 +0100 Subject: [PATCH 60/62] happy path for sale test + fixes --- pallets/subtensor/src/subnets/leasing.rs | 7 +- pallets/subtensor/src/tests/leasing.rs | 193 ++++++++++++++++++----- pallets/subtensor/src/tests/mock.rs | 9 ++ 3 files changed, 168 insertions(+), 41 deletions(-) diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index 09070849c1..1968cf3c64 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -223,7 +223,7 @@ impl Pallet { let now = >::block_number(); let delay = ColdkeySwapAnnouncementDelay::::get(); ensure!( - now > when.saturating_add(delay), + now >= when.saturating_add(delay), Error::::SubnetLeaseIntoSaleSettledTooEarly ); @@ -248,7 +248,8 @@ impl Pallet { for (contributor, amount) in contributions { // Compute the share of the contributor to the lease - let share: U64F64 = U64F64::from(amount).saturating_div(U64F64::from(crowdloan.raised)); + let denominator = U64F64::from(crowdloan.raised.saturating_sub(crowdloan.deposit)); + let share: U64F64 = U64F64::from(amount).saturating_div(denominator); SubnetLeaseShares::::insert(lease_id, &contributor, share); } @@ -260,7 +261,7 @@ impl Pallet { )?; Self::deposit_event(Event::SubnetLeaseCreated { - beneficiary: who, + beneficiary: beneficiary.clone(), lease_id, netuid, end_block, diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 2934a05b79..8d4ba6abf5 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -41,6 +41,9 @@ fn test_register_leased_network_works() { assert_eq!(lease.emissions_share, emissions_share); assert_eq!(lease.end_block, Some(end_block)); + // Ensure the lease owns the subnet + assert_eq!(SubnetOwner::::get(lease.netuid), lease.coldkey); + // Ensure the subnet exists assert!(SubnetMechanism::::contains_key(lease.netuid)); @@ -54,6 +57,7 @@ fn test_register_leased_network_works() { assert!(PROXIES.with_borrow(|proxies| proxies.0 == vec![(lease.coldkey, beneficiary)])); // Ensure the lease shares have been created for each contributor + let contributor1_share = U64F64::from(contributions[0].1).saturating_div(U64F64::from(cap)); assert_eq!( SubnetLeaseShares::::get(lease_id, contributions[0].0), @@ -64,6 +68,14 @@ fn test_register_leased_network_works() { SubnetLeaseShares::::get(lease_id, contributions[1].0), contributor2_share ); + let shares_count = SubnetLeaseShares::::iter_prefix(lease_id).count(); + assert_eq!(shares_count, 2); + + // Ensure the beneficiary has no lease shares because computed dynamically + assert!(!SubnetLeaseShares::::contains_key( + lease_id, + beneficiary + )); // Ensure the lease hotkey has 0 take from staking assert_eq!(SubtensorModule::get_hotkey_take(&lease.hotkey), 0); @@ -1039,23 +1051,23 @@ fn test_distribute_lease_network_dividends_accumulates_if_insufficient_liquidity fn test_announce_subnet_sale_into_lease_works() { new_test_ext(1).execute_with(|| { let crowdloan_id = 0; - let creator = U256::from(1); + let seller = U256::from(1); setup_crowdloan!( crowdloan_id, 0, 100_000_000, - creator, + seller, vec![] as Vec<(U256, u64)> ); let netuid = NetUid::from(1); add_network(netuid, 1, 0); - SubnetOwner::::insert(netuid, creator); + SubnetOwner::::insert(netuid, seller); let beneficiary = U256::from(2); let min_sale_price = TaoCurrency::from(100_000_000); assert_ok!(SubtensorModule::announce_subnet_sale_into_lease( - RuntimeOrigin::signed(creator), + RuntimeOrigin::signed(seller), netuid, beneficiary, min_sale_price, @@ -1064,7 +1076,7 @@ fn test_announce_subnet_sale_into_lease_works() { let now = frame_system::Pallet::::block_number(); assert_eq!( SubnetSaleIntoLeaseAnnouncements::::iter().collect::>(), - vec![(creator, (now, beneficiary, netuid, crowdloan_id))] + vec![(seller, (now, beneficiary, netuid, crowdloan_id))] ); assert_last_event::(RuntimeEvent::SubtensorModule( Event::SubnetSaleIntoLeaseAnnounced { @@ -1109,7 +1121,7 @@ fn test_announce_subnet_sale_into_lease_fails_if_bad_origin() { fn test_announce_subnet_sale_into_lease_fails_if_coldkey_swap_announcement_exists() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); - let creator = U256::from(1); + let seller = U256::from(1); let beneficiary = U256::from(2); let beneficiary_hash = ::Hashing::hash_of(&beneficiary); let now = frame_system::Pallet::::block_number(); @@ -1118,15 +1130,15 @@ fn test_announce_subnet_sale_into_lease_fails_if_coldkey_swap_announcement_exist 0, 0, min_sale_price.to_u64(), - creator, + seller, vec![] as Vec<(U256, u64)> ); - ColdkeySwapAnnouncements::::insert(creator, (now, beneficiary_hash)); + ColdkeySwapAnnouncements::::insert(seller, (now, beneficiary_hash)); assert_noop!( SubtensorModule::announce_subnet_sale_into_lease( - RuntimeOrigin::signed(creator), + RuntimeOrigin::signed(seller), netuid, beneficiary, min_sale_price, @@ -1141,7 +1153,7 @@ fn test_announce_subnet_sale_into_lease_fails_if_subnet_sale_into_lease_announce new_test_ext(1).execute_with(|| { let crowdloan_id = 0; let netuid = NetUid::from(1); - let creator = U256::from(1); + let seller = U256::from(1); let beneficiary = U256::from(2); let now = frame_system::Pallet::::block_number(); let min_sale_price = TaoCurrency::from(100_000_000); @@ -1149,18 +1161,18 @@ fn test_announce_subnet_sale_into_lease_fails_if_subnet_sale_into_lease_announce crowdloan_id, 0, min_sale_price.to_u64(), - creator, + seller, vec![] as Vec<(U256, u64)> ); SubnetSaleIntoLeaseAnnouncements::::insert( - creator, + seller, (now, beneficiary, netuid, crowdloan_id), ); assert_noop!( SubtensorModule::announce_subnet_sale_into_lease( - RuntimeOrigin::signed(creator), + RuntimeOrigin::signed(seller), netuid, beneficiary, min_sale_price, @@ -1174,14 +1186,14 @@ fn test_announce_subnet_sale_into_lease_fails_if_subnet_sale_into_lease_announce fn test_announce_subnet_sale_into_lease_fails_if_caller_doesnt_owns_subnet() { new_test_ext(1).execute_with(|| { let crowdloan_id = 0; - let creator = U256::from(1); + let seller = U256::from(1); let beneficiary = U256::from(2); let min_sale_price = TaoCurrency::from(100_000_000); setup_crowdloan!( crowdloan_id, 0, min_sale_price.to_u64(), - creator, + seller, vec![] as Vec<(U256, u64)> ); @@ -1191,7 +1203,7 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_doesnt_owns_subnet() { assert_noop!( SubtensorModule::announce_subnet_sale_into_lease( - RuntimeOrigin::signed(creator), + RuntimeOrigin::signed(seller), netuid, beneficiary, min_sale_price, @@ -1205,27 +1217,27 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_doesnt_owns_subnet() { fn test_announce_subnet_sale_into_lease_fails_if_caller_owns_multiple_subnets() { new_test_ext(1).execute_with(|| { let crowdloan_id = 0; - let creator = U256::from(1); + let seller = U256::from(1); let beneficiary = U256::from(2); let min_sale_price = TaoCurrency::from(100_000_000); setup_crowdloan!( crowdloan_id, 0, min_sale_price.to_u64(), - creator, + seller, vec![] as Vec<(U256, u64)> ); let netuid1 = NetUid::from(1); add_network(netuid1, 1, 0); - SubnetOwner::::insert(netuid1, creator); + SubnetOwner::::insert(netuid1, seller); let netuid2 = NetUid::from(2); add_network(netuid2, 1, 0); - SubnetOwner::::insert(netuid2, creator); + SubnetOwner::::insert(netuid2, seller); assert_noop!( SubtensorModule::announce_subnet_sale_into_lease( - RuntimeOrigin::signed(creator), + RuntimeOrigin::signed(seller), netuid1, beneficiary, min_sale_price, @@ -1235,12 +1247,117 @@ fn test_announce_subnet_sale_into_lease_fails_if_caller_owns_multiple_subnets() }); } +#[test] +fn test_settle_subnet_sale_into_lease_works() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let deposit = 10_000_000_000; + let seller = U256::from(1); + let beneficiary = U256::from(2); + let now = frame_system::Pallet::::block_number(); + let min_sale_price = TaoCurrency::from(100_000_000_000); + let contributions = vec![ + (beneficiary, 80_000_000_000u64), + (U256::from(3), 10_000_000_000), + (U256::from(4), 10_000_000_000), + ]; + setup_crowdloan!( + crowdloan_id, + deposit, + min_sale_price.to_u64(), + seller, + contributions.clone() + ); + + let netuid = NetUid::from(1); + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, seller); + + SubnetSaleIntoLeaseAnnouncements::::insert( + seller, + (now, beneficiary, netuid, crowdloan_id), + ); + + let delay = ColdkeySwapAnnouncementDelay::::get(); + run_to_block(now + delay); + + assert_ok!(SubtensorModule::settle_subnet_sale_into_lease( + RuntimeOrigin::signed(seller), + )); + + // Ensure the lease was created + let lease_id = 0; + let lease = SubnetLeases::::get(lease_id).unwrap(); + assert_eq!(lease.beneficiary, beneficiary); + assert_eq!(lease.emissions_share, Percent::from_percent(100)); + assert_eq!(lease.end_block, None); + + // Ensure the lease owns the subnet + assert_eq!(SubnetOwner::::get(lease.netuid), lease.coldkey); + + // Ensure the subnet uid lease id mapping exists + assert_eq!(SubnetUidToLeaseId::::get(netuid), Some(lease_id)); + + // Ensure the beneficiary has been added as a proxy + assert!(PROXIES.with_borrow(|proxies| proxies.0 == vec![(lease.coldkey, beneficiary)])); + + // Ensure lease shares have been created for each contributor + let contributor1_share = U64F64::from(contributions[1].1) + .saturating_div(U64F64::from(min_sale_price.to_u64() - deposit)); + assert_eq!( + SubnetLeaseShares::::get(lease_id, contributions[1].0), + contributor1_share + ); + let contributor2_share = U64F64::from(contributions[2].1) + .saturating_div(U64F64::from(min_sale_price.to_u64() - deposit)); + assert_eq!( + SubnetLeaseShares::::get(lease_id, contributions[2].0), + contributor2_share + ); + let shares_count = SubnetLeaseShares::::iter_prefix(lease_id).count(); + assert_eq!(shares_count, 2); + + // Ensure the beneficiary has no lease shares because computed dynamically + assert!(!SubnetLeaseShares::::contains_key( + lease_id, + beneficiary + )); + + // Ensure the lease hotkey has 0 take from staking + assert_eq!(SubtensorModule::get_hotkey_take(&lease.hotkey), 0); + + // Ensure the seller has been paid the leftover sale price + assert_eq!( + SubtensorModule::get_coldkey_balance(&seller), + min_sale_price.to_u64() - lease.cost + ); + + // Ensure events are emitted + assert_eq!( + nth_last_event(1), + RuntimeEvent::SubtensorModule(Event::SubnetLeaseCreated { + beneficiary, + lease_id, + netuid, + end_block: None + }) + ); + assert_eq!( + nth_last_event(0), + RuntimeEvent::SubtensorModule(Event::SubnetSaleIntoLeaseSettled { + beneficiary, + netuid + }) + ); + }); +} + #[test] fn test_cancel_subnet_sale_into_lease_works() { new_test_ext(1).execute_with(|| { let crowdloan_id = 0; let deposit = 10_000_000_000; - let creator = U256::from(1); + let seller = U256::from(1); let beneficiary = U256::from(2); let now = frame_system::Pallet::::block_number(); let min_sale_price = TaoCurrency::from(100_000_000_000); @@ -1248,19 +1365,19 @@ fn test_cancel_subnet_sale_into_lease_works() { crowdloan_id, deposit, min_sale_price.to_u64(), - creator, + seller, vec![ (beneficiary, 80_000_000_000u64), - (U256::from(3), 100_000_000) + (U256::from(3), 10_000_000_000) ] ); let netuid = NetUid::from(1); add_network(netuid, 1, 0); - SubnetOwner::::insert(netuid, creator); + SubnetOwner::::insert(netuid, seller); SubnetSaleIntoLeaseAnnouncements::::insert( - creator, + seller, (now, beneficiary, netuid, crowdloan_id), ); pallet_crowdloan::Crowdloans::::mutate(crowdloan_id, |crowdloan| { @@ -1271,12 +1388,12 @@ fn test_cancel_subnet_sale_into_lease_works() { // We can't refund a finalized crowdloan assert_noop!( - pallet_crowdloan::Pallet::::refund(RuntimeOrigin::signed(creator), crowdloan_id), + pallet_crowdloan::Pallet::::refund(RuntimeOrigin::signed(seller), crowdloan_id), pallet_crowdloan::Error::::AlreadyFinalized ); assert_ok!(SubtensorModule::cancel_subnet_sale_into_lease( - RuntimeOrigin::signed(creator), + RuntimeOrigin::signed(seller), )); assert_eq!( @@ -1289,7 +1406,7 @@ fn test_cancel_subnet_sale_into_lease_works() { // We can emit refunds for the crowdloan assert_ok!(pallet_crowdloan::Pallet::::refund( - RuntimeOrigin::signed(creator), + RuntimeOrigin::signed(seller), crowdloan_id )); }); @@ -1299,12 +1416,12 @@ fn test_cancel_subnet_sale_into_lease_works() { fn test_cancel_subnet_sale_into_lease_fails_if_bad_origin() { new_test_ext(1).execute_with(|| { let crowdloan_id = 0; - let creator = U256::from(1); + let seller = U256::from(1); setup_crowdloan!( crowdloan_id, 0, 100_000_000, - creator, + seller, vec![] as Vec<(U256, u64)> ); @@ -1324,17 +1441,17 @@ fn test_cancel_subnet_sale_into_lease_fails_if_bad_origin() { fn test_cancel_subnet_sale_into_lease_fails_if_no_subnet_sale_into_lease_announcement_exists() { new_test_ext(1).execute_with(|| { let crowdloan_id = 0; - let creator = U256::from(1); + let seller = U256::from(1); setup_crowdloan!( crowdloan_id, 0, 100_000_000, - creator, + seller, vec![] as Vec<(U256, u64)> ); assert_noop!( - SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::signed(creator)), + SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::signed(seller)), Error::::SubnetSaleIntoLeaseAnnouncementNotFound ); }); @@ -1344,7 +1461,7 @@ fn test_cancel_subnet_sale_into_lease_fails_if_no_subnet_sale_into_lease_announc fn test_cancel_subnet_sale_into_lease_fails_if_crowdloan_does_not_exists() { new_test_ext(1).execute_with(|| { let crowdloan_id = 0; - let creator = U256::from(1); + let seller = U256::from(1); let beneficiary = U256::from(2); let now = frame_system::Pallet::::block_number(); let netuid = NetUid::from(1); @@ -1352,18 +1469,18 @@ fn test_cancel_subnet_sale_into_lease_fails_if_crowdloan_does_not_exists() { crowdloan_id, 0, 100_000_000, - creator, + seller, vec![] as Vec<(U256, u64)> ); SubnetSaleIntoLeaseAnnouncements::::insert( - creator, + seller, // Bogus crowdloan id (now, beneficiary, netuid, crowdloan_id + 1), ); assert_noop!( - SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::signed(creator)), + SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::signed(seller)), pallet_crowdloan::Error::::InvalidCrowdloanId ); }); diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index f021d9d721..6d375af829 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -987,6 +987,15 @@ pub(crate) fn last_event() -> RuntimeEvent { System::events().pop().expect("RuntimeEvent expected").event } +pub(crate) fn nth_last_event(n: usize) -> RuntimeEvent { + System::events() + .into_iter() + .rev() + .nth(n) + .expect("RuntimeEvent expected") + .event +} + pub fn assert_last_event( generic_event: ::RuntimeEvent, ) { From 691d95fcc30cf877376dee9990b207022da2734f Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 15 Dec 2025 16:11:46 +0100 Subject: [PATCH 61/62] mkore tests error cases --- pallets/subtensor/src/tests/leasing.rs | 93 +++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 8d4ba6abf5..428097136b 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -1352,6 +1352,89 @@ fn test_settle_subnet_sale_into_lease_works() { }); } +#[test] +fn test_settle_subnet_sale_into_lease_fails_if_bad_origin() { + new_test_ext(1).execute_with(|| { + assert_noop!( + SubtensorModule::settle_subnet_sale_into_lease(RuntimeOrigin::none()), + DispatchError::BadOrigin + ); + assert_noop!( + SubtensorModule::settle_subnet_sale_into_lease(RuntimeOrigin::root()), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_settle_subnet_sale_into_lease_fails_if_no_announcement_exists() { + new_test_ext(1).execute_with(|| { + let seller = U256::from(1); + + assert_noop!( + SubtensorModule::settle_subnet_sale_into_lease(RuntimeOrigin::signed(seller)), + Error::::SubnetSaleIntoLeaseAnnouncementNotFound + ); + }); +} + +#[test] +fn test_settle_subnet_sale_into_lease_fails_if_announcement_delay_not_passed() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let deposit = 10_000_000_000; + let seller = U256::from(1); + let beneficiary = U256::from(2); + let now = frame_system::Pallet::::block_number(); + let min_sale_price = TaoCurrency::from(100_000_000_000); + setup_crowdloan!( + crowdloan_id, + deposit, + min_sale_price.to_u64(), + seller, + vec![(beneficiary, 90_000_000_000u64)] + ); + + let netuid = NetUid::from(1); + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, seller); + + SubnetSaleIntoLeaseAnnouncements::::insert( + seller, + (now, beneficiary, netuid, crowdloan_id), + ); + + assert_noop!( + SubtensorModule::settle_subnet_sale_into_lease(RuntimeOrigin::signed(seller)), + Error::::SubnetLeaseIntoSaleSettledTooEarly + ); + }); +} + +#[test] +fn test_settle_subnet_sale_into_lease_fails_if_crowdloan_does_not_exist() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let netuid = NetUid::from(1); + let seller = U256::from(1); + let beneficiary = U256::from(2); + let now = frame_system::Pallet::::block_number(); + + SubnetSaleIntoLeaseAnnouncements::::insert( + seller, + (now, beneficiary, netuid, crowdloan_id), + ); + + let delay = ColdkeySwapAnnouncementDelay::::get(); + run_to_block(now + delay); + + assert_noop!( + SubtensorModule::settle_subnet_sale_into_lease(RuntimeOrigin::signed(seller)), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + #[test] fn test_cancel_subnet_sale_into_lease_works() { new_test_ext(1).execute_with(|| { @@ -1465,18 +1548,10 @@ fn test_cancel_subnet_sale_into_lease_fails_if_crowdloan_does_not_exists() { let beneficiary = U256::from(2); let now = frame_system::Pallet::::block_number(); let netuid = NetUid::from(1); - setup_crowdloan!( - crowdloan_id, - 0, - 100_000_000, - seller, - vec![] as Vec<(U256, u64)> - ); SubnetSaleIntoLeaseAnnouncements::::insert( seller, - // Bogus crowdloan id - (now, beneficiary, netuid, crowdloan_id + 1), + (now, beneficiary, netuid, crowdloan_id), ); assert_noop!( From 0d8a5296dc0824d1d63d2c2f659bbe8ed1824b37 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 15 Dec 2025 16:44:18 +0100 Subject: [PATCH 62/62] prevent coldkey action during sale --- pallets/subtensor/src/macros/dispatches.rs | 5 + pallets/subtensor/src/tests/leasing.rs | 151 +++++++++++++++++- pallets/subtensor/src/tests/swap_coldkey.rs | 3 + .../subtensor/src/transaction_extension.rs | 27 +++- 4 files changed, 176 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 60bc73d358..6941278abc 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2375,6 +2375,11 @@ mod dispatches { let who = ensure_signed(origin)?; let now = >::block_number(); + ensure!( + !SubnetSaleIntoLeaseAnnouncements::::contains_key(&who), + Error::::SubnetSaleIntoLeaseAnnouncementAlreadyExists + ); + if let Some((when, _)) = ColdkeySwapAnnouncements::::get(who.clone()) { let reannouncement_delay = ColdkeySwapReannouncementDelay::::get(); let new_when = when.saturating_add(reannouncement_delay); diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 428097136b..0f0199519d 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -4,10 +4,14 @@ clippy::indexing_slicing )] use super::mock::*; +use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; -use frame_support::{StorageDoubleMap, assert_noop, assert_ok}; +use frame_support::{StorageDoubleMap, assert_noop, assert_ok, dispatch::GetDispatchInfo}; use sp_core::U256; -use sp_runtime::{Percent, traits::Hash}; +use sp_runtime::{ + Percent, + traits::{DispatchTransaction, Hash}, +}; use substrate_fixed::types::U64F64; use subtensor_runtime_common::AlphaCurrency; @@ -1561,6 +1565,149 @@ fn test_cancel_subnet_sale_into_lease_fails_if_crowdloan_does_not_exists() { }); } +#[test] +fn test_subtensor_extension_rejects_any_call_that_is_not_settle_subnet_or_cancel_subnet_sale_into_lease() + { + new_test_ext(0).execute_with(|| { + let crowdloan_id = 0; + let deposit = 10_000_000_000; + let seller = U256::from(1); + let beneficiary = U256::from(2); + let now = frame_system::Pallet::::block_number(); + let min_sale_price = TaoCurrency::from(100_000_000_000); + let hotkey = U256::from(2); + let stake = DefaultMinStake::::get().to_u64(); + setup_crowdloan!( + crowdloan_id, + deposit, + min_sale_price.to_u64(), + seller, + vec![ + (beneficiary, 80_000_000_000u64), + (U256::from(3), 10_000_000_000) + ] + ); + + let netuid = NetUid::from(1); + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, seller); + + // Setup sale announcement + SubnetSaleIntoLeaseAnnouncements::::insert( + seller, + (now, beneficiary, netuid, crowdloan_id), + ); + + let delay = ColdkeySwapAnnouncementDelay::::get(); + run_to_block(now + delay); + + let forbidden_calls: Vec = vec![ + RuntimeCall::SubtensorModule(SubtensorCall::dissolve_network { + netuid, + coldkey: seller, + }), + RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey, + netuid, + amount_staked: stake.into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::add_stake_limit { + hotkey, + netuid, + amount_staked: stake.into(), + limit_price: stake.into(), + allow_partial: false, + }), + RuntimeCall::SubtensorModule(SubtensorCall::swap_stake { + hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::swap_stake_limit { + hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + limit_price: stake.into(), + allow_partial: false, + }), + RuntimeCall::SubtensorModule(SubtensorCall::move_stake { + origin_hotkey: hotkey, + destination_hotkey: hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::transfer_stake { + destination_coldkey: beneficiary, + hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::remove_stake { + hotkey, + netuid, + amount_unstaked: (DefaultMinStake::::get().to_u64() * 2).into(), + }), + RuntimeCall::SubtensorModule(SubtensorCall::remove_stake_limit { + hotkey, + netuid, + amount_unstaked: (stake * 2).into(), + limit_price: 123456789.into(), + allow_partial: true, + }), + RuntimeCall::SubtensorModule(SubtensorCall::burned_register { netuid, hotkey }), + RuntimeCall::Balances(BalancesCall::transfer_all { + dest: beneficiary, + keep_alive: false, + }), + RuntimeCall::Balances(BalancesCall::transfer_keep_alive { + dest: beneficiary, + value: 100_000_000_000, + }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: beneficiary, + value: 100_000_000_000, + }), + ]; + + for call in forbidden_calls { + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_noop!( + ext.dispatch_transaction(RuntimeOrigin::signed(seller).into(), call, &info, 0, 0), + CustomTransactionError::ColdkeySwapAnnounced + ); + } + + // Settle subnet sale into lease should succeed + let call = RuntimeCall::SubtensorModule(SubtensorCall::settle_subnet_sale_into_lease {}); + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_ok!(ext.dispatch_transaction( + RuntimeOrigin::signed(seller).into(), + call, + &info, + 0, + 0 + )); + + // Cancel subnet sale into lease should succeed + let call = RuntimeCall::SubtensorModule(SubtensorCall::cancel_subnet_sale_into_lease {}); + let info = call.get_dispatch_info(); + let ext = SubtensorTransactionExtension::::new(); + assert_ok!(ext.dispatch_transaction( + RuntimeOrigin::signed(seller).into(), + call, + &info, + 0, + 0 + )); + }); +} + #[macro_export] macro_rules! setup_crowdloan { ($id:expr, $deposit:expr, $cap:expr, $creator:expr, $contributions:expr) => { diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 69ca8b3651..7e149a5834 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -204,10 +204,13 @@ fn test_announce_coldkey_swap_fails_if_subnet_sale_into_lease_announcement_exist let who = U256::from(1); let new_coldkey = U256::from(2); let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); let now = System::block_number(); let netuid = NetUid::from(1); let crowdloan_id = 0; + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost); + SubnetSaleIntoLeaseAnnouncements::::insert( who, (now, new_coldkey, netuid, crowdloan_id), diff --git a/pallets/subtensor/src/transaction_extension.rs b/pallets/subtensor/src/transaction_extension.rs index 32eb057f98..d83799ddb2 100644 --- a/pallets/subtensor/src/transaction_extension.rs +++ b/pallets/subtensor/src/transaction_extension.rs @@ -1,6 +1,6 @@ use crate::{ BalancesCall, Call, ColdkeySwapAnnouncements, Config, CustomTransactionError, Error, Pallet, - TransactionType, + SubnetSaleIntoLeaseAnnouncements, TransactionType, }; use codec::{Decode, DecodeWithMemTracking, Encode}; use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; @@ -83,6 +83,23 @@ where }) } } + + // Check if the origin coldkey is announced for a swap or sale + pub fn should_prevent_coldkey_action(who: &T::AccountId, call: &CallOf) -> bool { + let has_sale_announced = SubnetSaleIntoLeaseAnnouncements::::contains_key(who) + && !matches!( + call.is_sub_type(), + Some(Call::settle_subnet_sale_into_lease { .. }) + | Some(Call::cancel_subnet_sale_into_lease { .. }) + ); + let has_swap_announced = ColdkeySwapAnnouncements::::contains_key(who) + && !matches!( + call.is_sub_type(), + Some(Call::swap_coldkey_announced { .. }) + ); + + has_sale_announced || has_swap_announced + } } impl @@ -115,13 +132,7 @@ where return Ok((Default::default(), (), origin)); }; - // Ensure the origin coldkey is not announced for a swap. - if ColdkeySwapAnnouncements::::contains_key(who) - && !matches!( - call.is_sub_type(), - Some(Call::swap_coldkey_announced { .. }) - ) - { + if Self::should_prevent_coldkey_action(who, call) { return Err(CustomTransactionError::ColdkeySwapAnnounced.into()); }