diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index 98ea096199..3583169d91 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -325,9 +325,8 @@ 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 InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + 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 @@ -397,8 +396,8 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = Preimage; - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; 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 7b8124144d..e9f29e184b 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 eebefcf3e9..875916ccb7 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1354,44 +1354,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. @@ -1618,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, @@ -2240,6 +2202,44 @@ pub mod pallet { pallet_subtensor::Pallet::::set_min_non_immune_uids(netuid, min); Ok(()) } + + /// Sets the announcement 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_announcement_delay( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + ensure_root(origin)?; + pallet_subtensor::Pallet::::set_coldkey_swap_announcement_delay(duration); + log::trace!("ColdkeySwapAnnouncementDelaySet( duration: {duration:?} )"); + Ok(()) + } + + /// Sets the reannouncement delay for coldkey swap. + #[pallet::call_index(86)] + #[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 0140808baa..7d61b55a5f 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -131,17 +131,14 @@ 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 InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + 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 @@ -210,8 +207,8 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = (); - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; 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 024871e60f..63baa58d8f 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1382,39 +1382,74 @@ 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(); 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_schedule_duration(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_schedule_duration( + 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_schedule_duration( - root, - new_duration + assert_ok!(AdminUtils::sudo_set_coldkey_swap_announcement_delay( + root, new_delay + )); + + // You might want to check for events here if your pallet emits them + System::assert_last_event(Event::ColdkeySwapAnnouncementDelaySet(new_delay).into()); + }); +} + +#[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::ColdkeySwapScheduleDurationSet(new_duration).into()); + System::assert_last_event(Event::ColdkeySwapReannouncementDelaySet(new_delay).into()); }); } 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/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 476be905e9..836801f764 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,62 @@ 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); + + 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); + } + + #[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 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 + 1u32.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] + _(RawOrigin::Signed(old_coldkey), new_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 hotkey1: T::AccountId = account("hotkey1", 0, 0); + 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,19 +481,6 @@ 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::Root, @@ -466,6 +490,18 @@ mod pallet_benchmarks { ); } + #[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() { let tempo: u16 = 0; diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index a85c5f37d4..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}; @@ -944,16 +945,16 @@ pub mod pallet { (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() + pub fn DefaultColdkeySwapAnnouncementDelay() -> BlockNumberFor { + T::InitialColdkeySwapAnnouncementDelay::get() } - /// Default value for coldkey swap reschedule duration + /// Default value for coldkey swap reannouncement delay. #[pallet::type_value] - pub fn DefaultColdkeySwapRescheduleDuration() -> BlockNumberFor { - T::InitialColdkeySwapRescheduleDuration::get() + pub fn DefaultColdkeySwapReannouncementDelay() -> BlockNumberFor { + T::InitialColdkeySwapReannouncementDelay::get() } /// Default value for applying pending items (e.g. childkeys). @@ -1012,15 +1013,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 { @@ -1077,16 +1069,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 = @@ -1363,16 +1345,21 @@ 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>; + + /// 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] + pub type ColdkeySwapAnnouncements = + StorageMap<_, Twox64Concat, T::AccountId, (BlockNumberFor, T::Hash), OptionQuery>; /// --- DMAP ( hot, netuid ) --> alpha | Returns the total amount of alpha a hotkey owns. #[pallet::storage] @@ -2289,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 = @@ -2422,7 +2420,7 @@ pub mod pallet { #[derive(Debug, PartialEq)] pub enum CustomTransactionError { - ColdkeyInSwapSchedule, + ColdkeySwapAnnounced, StakeAmountTooLow, BalanceTooLow, SubnetNotExists, @@ -2449,7 +2447,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, diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index a735bde1e1..ee42feb117 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -214,16 +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 InitialColdkeySwapScheduleDuration: Get>; - /// Coldkey swap reschedule duration. + type InitialColdkeySwapAnnouncementDelay: Get>; + /// Coldkey swap reannouncement delay. #[pallet::constant] - type InitialColdkeySwapRescheduleDuration: Get>; + 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 61d5523285..6941278abc 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, Saturating, traits::Hash}; use crate::MAX_CRV3_COMMIT_SIZE_BYTES; use crate::MAX_NUM_ROOT_CLAIMS; @@ -1066,36 +1065,32 @@ 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. /// - /// # 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. + /// 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::from_parts(161_700_000, 0) - .saturating_add(T::DbWeight::get().reads(16_u64)) - .saturating_add(T::DbWeight::get().writes(11_u64)), DispatchClass::Operational, Pays::Yes))] + #[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, new_coldkey: T::AccountId, swap_cost: TaoCurrency, - ) -> DispatchResultWithPostInfo { - // Ensure it's called with root privileges (scheduler has root privileges) + ) -> DispatchResult { ensure_root(origin)?; - log::debug!("swap_coldkey: {:?} -> {:?}", old_coldkey, new_coldkey); - 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); + + Ok(()) } /// Sets the childkey take for a given hotkey. @@ -1332,94 +1327,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. @@ -2431,5 +2347,145 @@ mod dispatches { Ok(()) } + + /// 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) + .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, + ) -> DispatchResult { + 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); + ensure!(now >= new_when, Error::::ColdkeySwapReannouncedTooEarly); + } else { + let swap_cost = Self::get_key_swap_cost(); + Self::charge_swap_cost(&who, swap_cost)?; + } + + 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, + new_coldkey_hash, + }); + Ok(()) + } + + /// 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) + .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, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + 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(); + ensure!(now >= when, Error::::ColdkeySwapTooEarly); + + Self::do_swap_coldkey(&who, &new_coldkey)?; + + Ok(()) + } + + /// Removes a coldkey swap announcement for a coldkey. + /// + /// 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) + .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, + ) -> DispatchResult { + ensure_root(origin)?; + 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, + min_sale_price: TaoCurrency, + ) -> DispatchResult { + Self::do_announce_subnet_sale_into_lease(origin, netuid, beneficiary, min_sale_price) + } + + /// 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 { + 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/errors.rs b/pallets/subtensor/src/macros/errors.rs index 5a15330075..c10462ca70 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. @@ -150,10 +148,16 @@ 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, + /// 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 NewColdKeyIsHotkey, /// Childkey take is invalid. @@ -266,5 +270,19 @@ mod errors { InvalidRootClaimThreshold, /// Exceeded subnet limit number or zero. InvalidSubnetNumber, + /// Deprecated call. + Deprecated, + /// Subnet sale into lease announcement already exists + SubnetSaleIntoLeaseAnnouncementAlreadyExists, + /// Too many subnets owned + 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/macros/events.rs b/pallets/subtensor/src/macros/events.rs index a06e035d86..3a98158ecb 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -170,14 +170,24 @@ mod events { MaxDelegateTakeSet(u16), /// minimum delegate take is set by sudo/admin transaction MinDelegateTakeSet(u16), - /// A coldkey has been swapped + /// A coldkey swap announcement has been made. + ColdkeySwapAnnounced { + /// The account ID of the coldkey that made the announcement. + who: T::AccountId, + /// The hash of the new coldkey. + new_coldkey_hash: T::Hash, + }, + /// 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 + /// 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 { @@ -190,17 +200,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 @@ -222,15 +221,17 @@ 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 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. @@ -479,5 +480,29 @@ mod events { /// The amount of alpha distributed alpha: AlphaCurrency, }, + + /// A subnet sale into lease announcement has been made. + SubnetSaleIntoLeaseAnnounced { + /// 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/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/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..12fa3f5768 --- /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::{Saturating, 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.saturating_sub(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 4c9d5f01d1..27aefd7219 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_clear_rank_trust_pruning_maps; 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/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index a202a22a45..1968cf3c64 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}; @@ -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). @@ -77,60 +77,23 @@ 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 { 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); + let (lease_id, lease) = + Self::initialize_lease(&crowdloan, &who, emissions_share, end_block)?; - ::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)?; - - // 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, @@ -188,6 +151,147 @@ 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 + /// 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 { + 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 + /// 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)?; + + 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 emissions_share = Percent::from_percent(100); + let end_block = None; + let (lease_id, lease) = + Self::initialize_lease(&crowdloan, &beneficiary, emissions_share, end_block)?; + + Self::do_swap_coldkey(&who, &lease.coldkey)?; + + let leftover_cap = + Self::finalize_lease_creation(&crowdloan, lease_id, lease.clone(), netuid)?; + + // 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 != &who && contributor != &beneficiary); + + for (contributor, amount) in contributions { + // Compute the share of the contributor to the lease + 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); + } + + ::Currency::transfer( + &lease.coldkey, + &who, + leftover_cap, + Preservation::Expendable, + )?; + + Self::deposit_event(Event::SubnetLeaseCreated { + beneficiary: beneficiary.clone(), + lease_id, + netuid, + 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. @@ -347,6 +451,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())) @@ -369,26 +489,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. - 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::::InvalidCrowdloanId)?; - 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) } } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index c81138b58c..c7119fc066 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -1,228 +1,133 @@ 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. + /// 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, - ) -> 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); + // 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); } - // 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); - - // 8. Update the weight for the balance operations - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + 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); + Self::transfer_hotkeys_ownership(old_coldkey, new_coldkey); - // 9. Perform the actual coldkey swap - let _ = Self::perform_swap_coldkey(old_coldkey, new_coldkey, &mut weight); + // 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); + } - // 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, }); + 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); - // 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. - /// - /// 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. - pub fn perform_swap_coldkey( + /// 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, - 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)); + ) { + 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()); - }); - } + /// 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()); + 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 +136,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 +157,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 9cb94b4d1a..2f97ad63ab 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -1184,13 +1184,7 @@ 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 - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); // Check swapped keys claimed values diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 9f8bf4d6bf..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::{subnets::leasing::SubnetLeaseOf, *}; -use frame_support::{StorageDoubleMap, assert_err, assert_ok}; +use crate::transaction_extension::SubtensorTransactionExtension; +use crate::*; +use frame_support::{StorageDoubleMap, assert_noop, assert_ok, dispatch::GetDispatchInfo}; use sp_core::U256; -use sp_runtime::Percent; +use sp_runtime::{ + Percent, + traits::{DispatchTransaction, Hash}, +}; use substrate_fixed::types::U64F64; use subtensor_runtime_common::AlphaCurrency; @@ -23,7 +27,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; @@ -41,6 +45,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 +61,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 +72,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); @@ -94,16 +110,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 +125,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 +134,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 +152,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, @@ -163,7 +177,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); @@ -171,13 +185,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, ); }); } @@ -194,18 +208,18 @@ 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); - assert_err!( + assert_noop!( SubtensorModule::register_leased_network( RuntimeOrigin::signed(U256::from(2)), emissions_share, Some(end_block), ), - Error::::InvalidLeaseBeneficiary, + Error::::ExpectedBeneficiaryOrigin, ); }); } @@ -221,12 +235,12 @@ 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); - assert_err!( + assert_noop!( SubtensorModule::register_leased_network( RuntimeOrigin::signed(beneficiary), emissions_share, @@ -246,17 +260,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 @@ -286,14 +300,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 +315,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 +334,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, ); @@ -338,17 +350,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 @@ -359,7 +371,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, @@ -379,20 +391,20 @@ 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); SubtensorModule::create_account_if_non_existent(&beneficiary, &hotkey); // Terminate the lease - assert_err!( + assert_noop!( SubtensorModule::terminate_lease( RuntimeOrigin::signed(lease.beneficiary), lease_id, @@ -412,17 +424,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 @@ -430,7 +442,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, @@ -450,24 +462,24 @@ 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 run_to_block(end_block); // Terminate the lease - assert_err!( + assert_noop!( SubtensorModule::terminate_lease( RuntimeOrigin::signed(lease.beneficiary), lease_id, @@ -489,17 +501,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 +640,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 +711,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 +811,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 +901,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 +983,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 +1051,723 @@ 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, +#[test] +fn test_announce_subnet_sale_into_lease_works() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let seller = U256::from(1); + setup_crowdloan!( + crowdloan_id, + 0, + 100_000_000, + seller, + vec![] as Vec<(U256, u64)> + ); + + let netuid = NetUid::from(1); + add_network(netuid, 1, 0); + 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(seller), + netuid, + beneficiary, + min_sale_price, + )); + + let now = frame_system::Pallet::::block_number(); + assert_eq!( + SubnetSaleIntoLeaseAnnouncements::::iter().collect::>(), + vec![(seller, (now, beneficiary, netuid, crowdloan_id))] + ); + assert_last_event::(RuntimeEvent::SubtensorModule( + Event::SubnetSaleIntoLeaseAnnounced { + beneficiary, + netuid, + min_sale_price, + }, + )); + }); +} + +#[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 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 + ); + + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::root(), + netuid, + beneficiary, + min_sale_price, + ), + 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 seller = U256::from(1); + let beneficiary = U256::from(2); + 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(), + seller, + vec![] as Vec<(U256, u64)> + ); + + ColdkeySwapAnnouncements::::insert(seller, (now, beneficiary_hash)); + + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::signed(seller), + netuid, + beneficiary, + min_sale_price, + ), + 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 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); + setup_crowdloan!( + crowdloan_id, + 0, + min_sale_price.to_u64(), + seller, + vec![] as Vec<(U256, u64)> + ); + + SubnetSaleIntoLeaseAnnouncements::::insert( + seller, + (now, beneficiary, netuid, crowdloan_id), + ); + + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::signed(seller), + netuid, + beneficiary, + min_sale_price, + ), + 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 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(), + seller, + 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(seller), + netuid, + beneficiary, + min_sale_price, + ), + 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 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(), + seller, + vec![] as Vec<(U256, u64)> + ); + + let netuid1 = NetUid::from(1); + add_network(netuid1, 1, 0); + SubnetOwner::::insert(netuid1, seller); + let netuid2 = NetUid::from(2); + add_network(netuid2, 1, 0); + SubnetOwner::::insert(netuid2, seller); + + assert_noop!( + SubtensorModule::announce_subnet_sale_into_lease( + RuntimeOrigin::signed(seller), + netuid1, + beneficiary, + min_sale_price, + ), + Error::::TooManySubnetsOwned + ); + }); +} + +#[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_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)); + 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 + }) + ); + }); } -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() +#[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(|| { + 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, 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); + + SubnetSaleIntoLeaseAnnouncements::::insert( + seller, + (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(seller), crowdloan_id), + pallet_crowdloan::Error::::AlreadyFinalized + ); + + assert_ok!(SubtensorModule::cancel_subnet_sale_into_lease( + RuntimeOrigin::signed(seller), + )); + + 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(seller), + crowdloan_id )); - } + }); +} - (lease_id, lease) +#[test] +fn test_cancel_subnet_sale_into_lease_fails_if_bad_origin() { + new_test_ext(1).execute_with(|| { + let crowdloan_id = 0; + let seller = U256::from(1); + setup_crowdloan!( + crowdloan_id, + 0, + 100_000_000, + seller, + 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 seller = U256::from(1); + setup_crowdloan!( + crowdloan_id, + 0, + 100_000_000, + seller, + vec![] as Vec<(U256, u64)> + ); + + assert_noop!( + SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::signed(seller)), + 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 seller = U256::from(1); + let beneficiary = U256::from(2); + let now = frame_system::Pallet::::block_number(); + let netuid = NetUid::from(1); + + SubnetSaleIntoLeaseAnnouncements::::insert( + seller, + (now, beneficiary, netuid, crowdloan_id), + ); + + assert_noop!( + SubtensorModule::cancel_subnet_sale_into_lease(RuntimeOrigin::signed(seller)), + pallet_crowdloan::Error::::InvalidCrowdloanId + ); + }); +} + +#[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) => { + let funds_account = U256::from(42424242 + $id); + + pallet_crowdloan::Crowdloans::::insert( + $id, + pallet_crowdloan::CrowdloanInfo { + creator: $creator, + deposit: $deposit, + min_contribution: 100_000_000, + 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, + )); + + // 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) + }}; } diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 1d78b4dffd..c8542d733f 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}; @@ -2840,7 +2840,6 @@ fn test_migrate_reset_unactive_sn_idempotence() { }); } -#[test] fn test_migrate_remove_old_identity_maps() { let migration = crate::migrations::migrate_remove_old_identity_maps::migrate_remove_old_identity_maps::; @@ -2933,3 +2932,73 @@ fn test_migrate_remove_unknown_neuron_axon_cert_prom() { } } } + +#[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 090fcf8f75..6d375af829 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -212,9 +212,8 @@ 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 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 @@ -284,8 +283,8 @@ impl crate::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = Preimage; - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; @@ -988,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, ) { diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 9d3bdbfc62..7e149a5834 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -3,21 +3,22 @@ clippy::expect_used, clippy::indexing_slicing, clippy::panic, - clippy::unwrap_used + clippy::unwrap_used, + clippy::arithmetic_side_effects )] 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; 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}; -use sp_runtime::traits::{DispatchInfoOf, TransactionExtension}; +use sp_runtime::traits::Hash; +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}; @@ -27,314 +28,549 @@ use super::mock; 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 +use crate::{Call, Error}; + #[test] -fn test_swap_subnet_owner() { +fn test_announce_coldkey_swap_works() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); + let who = U256::from(1); let new_coldkey = U256::from(2); - let netuid = NetUid::from(1u16); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let left_over = 12345; - add_network(netuid, 1, 0); - SubnetOwner::::insert(netuid, old_coldkey); + assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight + 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!(SubnetOwner::::get(netuid), new_coldkey); + let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); + assert_eq!( + ColdkeySwapAnnouncements::::iter().collect::>(), + vec![(who, (now + delay, new_coldkey_hash))] + ); + assert_eq!(SubtensorModule::get_coldkey_balance(&who), left_over); + assert_eq!( + last_event(), + RuntimeEvent::SubtensorModule(Event::ColdkeySwapAnnounced { + who, + new_coldkey_hash, + }) + ); }); } -// 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() { +fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); + let who = 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 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 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_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); - 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().to_u64(); + SubtensorModule::add_balance_to_coldkey_account(&who, 2 * swap_cost); - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who), + new_coldkey_hash, )); + let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), - TaoCurrency::ZERO + ColdkeySwapAnnouncements::::iter().collect::>(), + vec![(who, (now + delay, new_coldkey_hash))] ); + + let reannouncement_delay = ColdkeySwapReannouncementDelay::::get(); + System::run_to_block::(now + delay + reannouncement_delay); + + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who), + new_coldkey_2_hash, + )); + + let now = System::block_number(); assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - total_stake_before_swap + ColdkeySwapAnnouncements::::iter().collect::>(), + vec![(who, (now + delay, new_coldkey_2_hash))] ); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_staking_hotkeys --exact --nocapture #[test] -fn test_swap_staking_hotkeys() { +fn test_announce_coldkey_swap_only_pays_swap_cost_if_no_announcement_exists() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); + let who = U256::from(1); let new_coldkey = U256::from(2); - let hotkey = U256::from(3); + 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; - StakingHotkeys::::insert(old_coldkey, vec![hotkey]); + 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 + ); - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight + assert_ok!(SubtensorModule::announce_coldkey_swap( + RuntimeOrigin::signed(who), + new_coldkey_hash, )); + assert_eq!(SubtensorModule::get_coldkey_balance(&who), left_over); - assert!(StakingHotkeys::::get(old_coldkey).is_empty()); - assert_eq!(StakingHotkeys::::get(new_coldkey), vec![hotkey]); + let now = System::block_number(); + 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), + new_coldkey_2_hash, + )); + assert_eq!(SubtensorModule::get_coldkey_balance(&who), left_over); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_hotkey_owners --exact --nocapture #[test] -fn test_swap_hotkey_owners() { +fn test_announce_coldkey_swap_with_bad_origin_fails() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); + 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_hash), + BadOrigin + ); + + assert_noop!( + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::root(), new_coldkey_hash), + BadOrigin + ); + }); +} + +#[test] +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 hotkey = U256::from(3); + 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); - Owner::::insert(hotkey, old_coldkey); - OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); + assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight + 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, )); - assert_eq!(Owner::::get(hotkey), new_coldkey); - assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); - assert_eq!(OwnedHotkeys::::get(new_coldkey), vec![hotkey]); + let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); + assert_eq!( + ColdkeySwapAnnouncements::::iter().collect::>(), + vec![(who, (now + delay, new_coldkey_hash))] + ); + + assert_noop!( + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_2_hash,), + Error::::ColdkeySwapReannouncedTooEarly + ); }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_transfer_remaining_balance --exact --nocapture + #[test] -fn test_transfer_remaining_balance() { +fn test_announce_coldkey_swap_fails_if_subnet_sale_into_lease_announcement_exists() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); + let who = U256::from(1); let new_coldkey = U256::from(2); - let balance = 100; + 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(&old_coldkey, balance); + SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost); - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight - )); + SubnetSaleIntoLeaseAnnouncements::::insert( + who, + (now, new_coldkey, netuid, crowdloan_id), + ); - assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance); + assert_noop!( + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_hash), + Error::::SubnetSaleIntoLeaseAnnouncementAlreadyExists + ); }); } -// 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() { +fn test_swap_coldkey_announced_works() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); + 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); + 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(); + + ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); + + // Run some blocks for the announcement to be past the delay + let delay = ColdkeySwapAnnouncementDelay::::get() + 1; + System::run_to_block::(now + delay); + + let ( + netuid1, + netuid2, + hotkeys, + hk1_alpha, + hk2_alpha, + hk3_alpha, + total_ck_stake, + identity, + balance_before, + total_stake_before, + ) = comprehensive_setup!( + who, + new_coldkey, + new_coldkey_hash, + left_over, + stake1, + stake2, + stake3, + hotkey1, + hotkey2, + hotkey3 + ); - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight + assert_ok!(SubtensorModule::swap_coldkey_announced( + ::RuntimeOrigin::signed(who), + new_coldkey )); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), - TaoCurrency::ZERO + comprehensive_checks!( + who, + hotkey1, + hotkey2, + hotkey3, + hotkeys, + new_coldkey, + balance_before, + left_over, + identity, + netuid1, + netuid2, + hk1_alpha, + hk2_alpha, + hk3_alpha, + total_ck_stake, + total_stake_before ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - TaoCurrency::ZERO + }); +} + +#[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::swap_coldkey_announced(RuntimeOrigin::none(), new_coldkey), + BadOrigin + ); + + assert_noop!( + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::root(), new_coldkey), + BadOrigin ); }); } -// 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() { +fn test_swap_coldkey_announced_without_announcement_fails() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); + let who = 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]); + assert_noop!( + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who), new_coldkey), + Error::::ColdkeySwapAnnouncementNotFound + ); + }) +} - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight - )); +#[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); + let now = System::block_number(); - assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); - assert_eq!( - OwnedHotkeys::::get(new_coldkey), - vec![hotkey1, hotkey2] + ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); + + assert_noop!( + SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who), other_coldkey), + Error::::AnnouncedColdkeyHashDoesNotMatch ); - }); + }) } -// 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() { +fn test_swap_coldkey_announced_too_early_fails() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); + let who = U256::from(1); let new_coldkey = U256::from(2); - let netuid1 = NetUid::from(1); - let netuid2 = NetUid::from(2); + let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - add_network(netuid1, 1, 0); - add_network(netuid2, 1, 0); - SubnetOwner::::insert(netuid1, old_coldkey); - SubnetOwner::::insert(netuid2, old_coldkey); + let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get(); + ColdkeySwapAnnouncements::::insert(who, (now + delay, new_coldkey_hash)); - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight + assert_noop!( + SubtensorModule::swap_coldkey_announced( + ::RuntimeOrigin::signed(who), + new_coldkey + ), + 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 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, )); - assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); - assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); - }); + let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get() + 1; + System::run_to_block::(now + delay); + + SubtensorModule::create_account_if_non_existent(&new_coldkey, &hotkey); + + assert_noop!( + SubtensorModule::swap_coldkey_announced( + ::RuntimeOrigin::signed(who), + new_coldkey + ), + Error::::ColdKeyAlreadyAssociated + ); + }) +} + +#[test] +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); + let hotkey = U256::from(3); + let hotkey_hash = ::Hashing::hash_of(&hotkey); + let now = System::block_number(); + + ColdkeySwapAnnouncements::::insert(who, (now, hotkey_hash)); + + let now = System::block_number(); + let delay = ColdkeySwapAnnouncementDelay::::get() + 1; + System::run_to_block::(now + delay); + + SubtensorModule::create_account_if_non_existent(&new_coldkey, &hotkey); + + assert_noop!( + SubtensorModule::swap_coldkey_announced( + ::RuntimeOrigin::signed(who), + hotkey + ), + Error::::NewColdKeyIsHotkey + ); + }) } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_zero_balance --exact --nocapture #[test] -fn test_swap_with_zero_balance() { +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 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 mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64()); + + 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, + left_over, + stake1, + stake2, + stake3, + hotkey1, + hotkey2, + hotkey3 + ); + + assert_ok!(SubtensorModule::swap_coldkey( + ::RuntimeOrigin::root(), + old_coldkey, + new_coldkey, + swap_cost, )); - assert_eq!(Balances::free_balance(old_coldkey), 0); - assert_eq!(Balances::free_balance(new_coldkey), 0); + comprehensive_checks!( + old_coldkey, + hotkey1, + hotkey2, + hotkey3, + hotkeys, + new_coldkey, + balance_before, + left_over, + identity, + netuid1, + netuid2, + hk1_alpha, + hk2_alpha, + hk3_alpha, + total_ck_stake, + total_stake_before + ); }); } -// 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() { +fn test_swap_coldkey_with_bad_origin_fails() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); + 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 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()); + let old_identity = ChainIdentityV2 { + name: b"Old identity".to_vec(), + ..Default::default() + }; + IdentitiesV2::::insert(who, old_identity.clone()); - // 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 new_identity = ChainIdentityV2 { + name: b"New identity".to_vec(), + ..Default::default() + }; + IdentitiesV2::::insert(new_coldkey, new_identity.clone()); - // Get stake before swap - let stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); + assert_ok!(SubtensorModule::do_swap_coldkey(&who, &new_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 - )); + // Identity is preserved + assert_eq!(IdentitiesV2::::get(who), Some(old_identity)); + assert_eq!(IdentitiesV2::::get(new_coldkey), Some(new_identity)); + }); +} + +#[test] +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); + + assert_noop!( + SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_hash), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + }); +} + +#[test] +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); + + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), @@ -342,14 +578,13 @@ fn test_swap_idempotency() { ); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - stake_before_swap + TaoCurrency::ZERO ); }); } -// 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() { +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); @@ -405,16 +640,10 @@ 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( + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); + assert_ok!(SubtensorModule::do_swap_coldkey( &old_coldkey2, &new_coldkey2, - &mut weight )); assert_eq!( @@ -438,93 +667,13 @@ 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() { +fn test_do_swap_coldkey_effect_on_delegated_stake() { 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 mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight - )); - - 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() - ); - }); -} - -// 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(|| { - 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 mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight - )); - - 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(|| { - let subnet_owner_coldkey = U256::from(1001); - let subnet_owner_hotkey = U256::from(1002); - let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); - + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); let delegator = U256::from(3); @@ -552,12 +701,7 @@ 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_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), @@ -577,314 +721,35 @@ 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 #[test] -fn test_swap_concurrent_modifications() { +fn test_swap_delegated_stake_for_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 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; 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 mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight - )); - - 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))); - }); -} - -// 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(|| { - 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 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 - 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(|| { - 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(); + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey1, other_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()); - // 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( - // <::RuntimeOrigin>::signed(old_coldkey), - &old_coldkey, - &new_coldkey, - swap_cost - )); - - // 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(), - ); - }); -} - -// 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(|| { - 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; - let mut weight = Weight::zero(); - - // 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 + // Notice hotkey1 and hotkey2 are Owned by other_coldkey + // old_coldkey and new_coldkey therefore delegates stake to them + // === Give old_coldkey some balance === 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 + // === Stake to hotkeys === assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(old_coldkey), hotkey1, @@ -897,6 +762,7 @@ fn test_swap_stake_for_coldkey() { netuid, ); + let (expected_stake_alpha2, fee) = mock::swap_tao_to_alpha(netuid, stake_amount2.into()); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(old_coldkey), hotkey2, @@ -908,49 +774,27 @@ fn test_swap_stake_for_coldkey() { &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, - ); + let fee = (expected_stake_alpha2.to_u64() as f64 * 0.003) as u64; // 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); - - // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); - - // 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() + let coldkey_stake = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); + let stake_coldkey_hotkey1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &old_coldkey, + netuid, ); - - // Verify ownership transfer - assert_eq!( - SubtensorModule::get_owned_hotkeys(&new_coldkey), - vec![hotkey1, hotkey2] + let stake_coldkey_hotkey2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &old_coldkey, + netuid, ); - assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![]); + let total_hotkey1_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey1); + let total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); + + // Perform the swap + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); // Verify stake transfer assert_eq!( @@ -959,7 +803,7 @@ fn test_swap_stake_for_coldkey() { &new_coldkey, netuid ), - expected_stake_alpha1 + expected_stake_alpha3 + expected_stake_alpha1 ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -986,207 +830,7 @@ fn test_swap_stake_for_coldkey() { 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" - ); - }); -} - -// 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(|| { - 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; - let mut weight = Weight::zero(); - - // 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, - ); - - // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); - - // Verify StakingHotkeys transfer - assert_eq!( - StakingHotkeys::::get(new_coldkey), - vec![hotkey1, hotkey2] - ); - assert_eq!(StakingHotkeys::::get(old_coldkey), vec![]); - }); -} - -// 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(|| { - 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; - let mut weight = Weight::zero(); - let netuid = NetUid::from(1); - - // Setup initial state - add_network(netuid, 1, 0); - register_ok_neuron(netuid, hotkey1, other_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()); - - // Notice hotkey1 and hotkey2 are Owned by other_coldkey - // old_coldkey and new_coldkey therefore delegates stake to them - // === Give old_coldkey some balance === - SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - stake_amount1 + stake_amount2 + 1_000_000, - ); - - // === 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, - ); - - let (expected_stake_alpha2, fee) = mock::swap_tao_to_alpha(netuid, stake_amount2.into()); - 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 fee = (expected_stake_alpha2.to_u64() as f64 * 0.003) as u64; - - // Record initial values - let initial_total_issuance = SubtensorModule::get_total_issuance(); - let initial_total_stake = SubtensorModule::get_total_stake(); - let coldkey_stake = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - let stake_coldkey_hotkey1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &old_coldkey, - netuid, - ); - let stake_coldkey_hotkey2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &old_coldkey, - netuid, - ); - let total_hotkey1_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey1); - let total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); - - // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); - - // Verify stake transfer - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &new_coldkey, - netuid - ), - expected_stake_alpha1 - ); - 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 TotalColdkeyStake + // Verify TotalColdkeyStake assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), coldkey_stake @@ -1220,85 +864,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(|| { - let old_coldkey = U256::from(1); - 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); - add_network(netuid2, 14, 0); - SubnetOwner::::insert(netuid1, old_coldkey); - SubnetOwner::::insert(netuid2, old_coldkey); - - // Set up TotalNetworks - TotalNetworks::::put(3); - - // Perform the swap - SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); - - // Verify the swap - assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); - assert_eq!(SubnetOwner::::get(netuid2), new_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(|| { - 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]); - - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - swap_cost.into() - )); - - // 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(|| { - 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); - }); -} - -// 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(|| { @@ -1523,12 +1088,7 @@ 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_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), ck_stake @@ -1617,9 +1177,8 @@ 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() { +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); @@ -1664,12 +1223,7 @@ fn test_coldkey_delegations() { )); // Perform the swap - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &coldkey, - &new_coldkey, - &mut weight - )); + 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); @@ -1706,911 +1260,422 @@ fn test_coldkey_delegations() { } #[test] -fn test_schedule_swap_coldkey_success() { +fn test_remove_coldkey_swap_announcement_works() { 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(); + 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(); - // Add balance to the old coldkey account - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64() + 1_000); + ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); - // Schedule the coldkey swap - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey + assert_ok!(SubtensorModule::remove_coldkey_swap_announcement( + RuntimeOrigin::root(), + who, )); - // 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 + assert!(!ColdkeySwapAnnouncements::::contains_key(who)); }); } -// 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() { +fn test_remove_coldkey_swap_announcement_with_bad_origin_fails() { 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); + let who = U256::from(1); + let coldkey = U256::from(2); - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey - )); + assert_noop!( + SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::signed(who), coldkey), + BadOrigin + ); - // Attempt to schedule again assert_noop!( - SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - new_coldkey - ), - Error::::SwapAlreadyScheduled + SubtensorModule::remove_coldkey_swap_announcement(RuntimeOrigin::none(), coldkey), + BadOrigin ); }); } -// 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(|| { - 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; +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 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); + // Setup reserves + let reserve = stake * 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(); - - // Schedule the swap - assert_ok!(SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(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); - - 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" - ); + // Setup network and neuron + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, who, 0); - 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" - ); + SubtensorModule::add_balance_to_coldkey_account(&who, u64::MAX); - // Check for the SwapExecuted event - System::assert_has_event( - Event::ColdkeySwapped { - old_coldkey, - new_coldkey, - swap_cost, - } - .into(), - ); + // Schedule the coldkey for a swap + assert_ok!(SubtensorModule::announce_coldkey_swap( + ::RuntimeOrigin::signed(who), + new_coldkey_hash, + )); + assert!(ColdkeySwapAnnouncements::::contains_key(who)); + + 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 { 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 --test swap_coldkey -- test_direct_swap_coldkey_call_fails --exact --nocapture #[test] -fn test_direct_swap_coldkey_call_fails() { +#[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::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 + <::RuntimeOrigin>::root(), + new_coldkey, ), - Error::::SwapAlreadyScheduled + Error::::Deprecated ); }); } -// 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 +#[macro_export] +macro_rules! comprehensive_setup { + ( + $who:expr, + $new_coldkey:expr, + $new_coldkey_hash:expr, + $left_over:expr, + $stake1:expr, + $stake2:expr, + $stake3:expr, + $hotkey1:expr, + $hotkey2:expr, + $hotkey3:expr + ) => {{ SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - swap_cost.to_u64() + 1_000 * 2, + &$who, + $stake1 + $stake2 + $stake3 + $left_over, ); - 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 + // 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)], ); - - // 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(|| { - 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()); - - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - burn_cost - )); - - 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 + AutoStakeDestinationColdkeys::::insert( + $hotkey2, + netuid2, + vec![U256::from(7), U256::from(8), $who], ); - }); -} - -#[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; + // 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); - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 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); - 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 + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed($who), + $hotkey1, + netuid1, + $stake1.into() )); - - // 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, - burn_cost + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed($who), + $hotkey2, + netuid2, + $stake2.into() )); - - // Ensure no identities have been changed - assert!(IdentitiesV2::::get(old_coldkey).is_none()); - assert!(IdentitiesV2::::get(new_coldkey).is_none()); - }); + 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, + ) + }}; } -#[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 - )); +#[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, + $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)); - 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![], - }; + // // 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 + // ); - 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()); + // Ensure the identity is correctly swapped + assert!(IdentitiesV2::::get($who).is_none()); + assert_eq!(IdentitiesV2::::get($new_coldkey), Some($identity)); - // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey, - &new_coldkey, - burn_cost - )); + // Ensure the subnet ownerships are correctly swapped + assert_eq!(SubnetOwner::::get($netuid1), $new_coldkey); + assert_eq!(SubnetOwner::::get($netuid2), $new_coldkey); - // Ensure no identities have been changed - assert!(IdentitiesV2::::get(old_coldkey).is_none()); - assert!(IdentitiesV2::::get(new_coldkey).is_some()); - }); -} - -// 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(|| { - let old_coldkey = U256::from(3); - let new_coldkey = U256::from(4); - let hotkey = U256::from(5); + // 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] + ); - let burn_cost = SubtensorModule::get_key_swap_cost(); - assert_noop!( - SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - 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 ), - Error::::NotEnoughBalanceToPaySwapColdKey + $hk1_alpha ); - }); -} - -// 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 - // correctly filters transactions that attempt to use funds - // while a coldkey swap is scheduled. - - 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 * 100; - - 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 - 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::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(who), - new_coldkey - )); - - assert!(ColdkeySwapScheduled::::contains_key(who)); - - // Setup the extension - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - let extension = SubtensorTransactionExtension::::new(); - - // Try each call - - // Add stake - let call = RuntimeCall::SubtensorModule(SubtensorCall::add_stake { - hotkey, - netuid, - amount_staked: 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() - ); - - // 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 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() - ); - - // Swap stake - let call = RuntimeCall::SubtensorModule(SubtensorCall::swap_stake { - hotkey, - origin_netuid: netuid, - 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() - ); - - // 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 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() - ); - - // 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 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() - ); - - // 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 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() - ); - - // Transfer all - let call = RuntimeCall::Balances(BalancesCall::transfer_all { - 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() - ); - - // Transfer keep alive - let call = RuntimeCall::Balances(BalancesCall::transfer_keep_alive { - 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() - ); - - // Transfer allow death - let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { - 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() - ); - - // 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() - ); - - 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 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() - ); - - // 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 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() - ); - - // 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); - }); -} - -// 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() { - // Testing the signed extension validate function - // correctly filters transactions that are critical - // while a coldkey swap is scheduled. - - 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::schedule_swap_coldkey( - <::RuntimeOrigin>::signed(who), - new_coldkey - )); - - assert!(ColdkeySwapScheduled::::contains_key(who)); - - // Setup the extension - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - let extension = SubtensorTransactionExtension::::new(); - - // Try each call - - // Dissolve network - 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() + 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, ); - }); -} - -#[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); + // Ensure the staking hotkeys are correctly swapped + assert!(StakingHotkeys::::get($who).is_empty()); + assert_eq!(StakingHotkeys::::get($new_coldkey), $hotkeys); - let mut weight = Weight::zero(); - assert_ok!(SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight - )); + // 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); - let new_coldkeys = AutoStakeDestinationColdkeys::::get(hotkey, netuid); - assert!(new_coldkeys.contains(&new_coldkey)); - assert!(!new_coldkeys.contains(&old_coldkey)); + // Ensure the remaining balance is transferred to the new coldkey + assert_eq!(SubtensorModule::get_coldkey_balance(&$who), 0); assert_eq!( - AutoStakeDestination::::try_get(old_coldkey, netuid), - Err(()) + SubtensorModule::get_coldkey_balance(&$new_coldkey), + $left_over ); + + // Ensure total stake is unchanged assert_eq!( - AutoStakeDestination::::try_get(new_coldkey, netuid), - Ok(hotkey) + 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, + } + .into(), + ); + }; } diff --git a/pallets/subtensor/src/transaction_extension.rs b/pallets/subtensor/src/transaction_extension.rs index cf1d410ea9..d83799ddb2 100644 --- a/pallets/subtensor/src/transaction_extension.rs +++ b/pallets/subtensor/src/transaction_extension.rs @@ -1,24 +1,27 @@ use crate::{ - BalancesCall, Call, ColdkeySwapScheduled, Config, CustomTransactionError, Error, Pallet, - TransactionType, + BalancesCall, Call, ColdkeySwapAnnouncements, Config, CustomTransactionError, Error, Pallet, + SubnetSaleIntoLeaseAnnouncements, 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 +34,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 +53,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, @@ -83,58 +83,63 @@ 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 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()); - } - } + if Self::should_prevent_coldkey_action(who, call) { + 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 +163,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 +208,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 +224,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 +238,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 +254,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 +281,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); } diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 10fc0535f0..609e43cf63 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -808,19 +808,14 @@ 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)); + } + + pub fn set_coldkey_swap_reannouncement_delay(duration: BlockNumberFor) { + ColdkeySwapReannouncementDelay::::set(duration); + Self::deposit_event(Event::ColdkeySwapReannouncementDelaySet(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 ee5b1693ba..d008c7fd0c 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -196,17 +196,14 @@ 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 InitialColdkeySwapAnnouncementDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + 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 @@ -275,8 +272,8 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = (); - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; + 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 9f54a46b54..168b9076f2 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,12 @@ 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 InitialColdkeySwapAnnouncementDelay: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days + 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 - // 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 @@ -1120,8 +1117,8 @@ impl pallet_subtensor::Config for Runtime { type Yuma3On = InitialYuma3On; type InitialTaoWeight = SubtensorInitialTaoWeight; type Preimages = Preimage; - type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; - type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall;