diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index baf711b85..3e06b822e 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -4,6 +4,7 @@ pub use pallet::*; pub mod weights; pub use weights::WeightInfo; +use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{traits::Member, RuntimeAppPublic}; mod benchmarking; @@ -1128,6 +1129,73 @@ pub mod pallet { Ok(()) } + + /// 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((0, DispatchClass::Operational, Pays::No))] + 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. + /// The dissolve network schedule determines how long it takes for a network dissolution operation to complete. + /// + /// # Arguments + /// * `origin` - The origin of the call, which must be the root account. + /// * `duration` - The new duration for the dissolve network 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(55)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_dissolve_network_schedule_duration( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + // Ensure the call is made by the root account + ensure_root(origin)?; + + // Set the duration of schedule dissolve network + pallet_subtensor::Pallet::::set_dissolve_network_schedule_duration(duration); + + // Log the change + log::trace!( + "DissolveNetworkScheduleDurationSet( duration: {:?} )", + duration + ); + + Ok(()) + } } } diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index af3bf66d7..8ab85f177 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -1,6 +1,6 @@ use frame_support::sp_runtime::DispatchError; use frame_support::{ - assert_err, assert_ok, + assert_err, assert_noop, assert_ok, dispatch::{DispatchClass, GetDispatchInfo, Pays}, }; use frame_system::Config; @@ -1361,3 +1361,77 @@ fn test_sudo_get_set_alpha() { )); }); } + +#[test] +fn test_sudo_set_coldkey_swap_schedule_duration() { + new_test_ext().execute_with(|| { + // Arrange + let root = RuntimeOrigin::root(); + let non_root = RuntimeOrigin::signed(U256::from(1)); + let new_duration = 100u32.into(); + + // Act & Assert: Non-root account should fail + assert_noop!( + AdminUtils::sudo_set_coldkey_swap_schedule_duration(non_root, new_duration), + DispatchError::BadOrigin + ); + + // Act: Root account should succeed + assert_ok!(AdminUtils::sudo_set_coldkey_swap_schedule_duration( + root.clone(), + new_duration + )); + + // Assert: Check if the duration was actually set + assert_eq!( + pallet_subtensor::ColdkeySwapScheduleDuration::::get(), + new_duration + ); + + // Act & Assert: Setting the same value again should succeed (idempotent operation) + assert_ok!(AdminUtils::sudo_set_coldkey_swap_schedule_duration( + root, + new_duration + )); + + // You might want to check for events here if your pallet emits them + System::assert_last_event(Event::ColdkeySwapScheduleDurationSet(new_duration).into()); + }); +} + +#[test] +fn test_sudo_set_dissolve_network_schedule_duration() { + new_test_ext().execute_with(|| { + // Arrange + let root = RuntimeOrigin::root(); + let non_root = RuntimeOrigin::signed(U256::from(1)); + let new_duration = 200u32.into(); + + // Act & Assert: Non-root account should fail + assert_noop!( + AdminUtils::sudo_set_dissolve_network_schedule_duration(non_root, new_duration), + DispatchError::BadOrigin + ); + + // Act: Root account should succeed + assert_ok!(AdminUtils::sudo_set_dissolve_network_schedule_duration( + root.clone(), + new_duration + )); + + // Assert: Check if the duration was actually set + assert_eq!( + pallet_subtensor::DissolveNetworkScheduleDuration::::get(), + new_duration + ); + + // Act & Assert: Setting the same value again should succeed (idempotent operation) + assert_ok!(AdminUtils::sudo_set_dissolve_network_schedule_duration( + root, + new_duration + )); + + // You might want to check for events here if your pallet emits them + System::assert_last_event(Event::DissolveNetworkScheduleDurationSet(new_duration).into()); + }); +} diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 153d84b89..d33e968e3 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -312,7 +312,7 @@ benchmarks! { let amount_to_be_staked = 100_000_000_000_000u64; Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked); assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into())); - }: dissolve_network(RawOrigin::Signed(coldkey), 1) + }: dissolve_network(RawOrigin::Root, coldkey, 1) // swap_hotkey { // let seed: u32 = 1; diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 39f745b93..14b1beb7f 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -992,30 +992,27 @@ impl Pallet { /// * 'SubNetworkDoesNotExist': If the specified network does not exist. /// * 'NotSubnetOwner': If the caller does not own the specified subnet. /// - pub fn user_remove_network(origin: T::RuntimeOrigin, netuid: u16) -> dispatch::DispatchResult { - // --- 1. Ensure the function caller is a signed user. - let coldkey = ensure_signed(origin)?; - - // --- 2. Ensure this subnet exists. + pub fn user_remove_network(coldkey: T::AccountId, netuid: u16) -> dispatch::DispatchResult { + // --- 1. Ensure this subnet exists. ensure!( Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist ); - // --- 3. Ensure the caller owns this subnet. + // --- 2. Ensure the caller owns this subnet. ensure!( SubnetOwner::::get(netuid) == coldkey, Error::::NotSubnetOwner ); - // --- 4. Explicitly erase the network and all its parameters. + // --- 2. Explicitly erase the network and all its parameters. Self::remove_network(netuid); - // --- 5. Emit the NetworkRemoved event. + // --- 3. Emit the NetworkRemoved event. log::debug!("NetworkRemoved( netuid:{:?} )", netuid); Self::deposit_event(Event::NetworkRemoved(netuid)); - // --- 6. Return success. + // --- 5. Return success. Ok(()) } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 6988d863a..d86bf4df8 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -682,7 +682,7 @@ mod dispatches { new_coldkey: T::AccountId, ) -> DispatchResultWithPostInfo { // Ensure it's called with root privileges (scheduler has root privileges) - ensure_root(origin.clone())?; + ensure_root(origin)?; log::info!("swap_coldkey: {:?} -> {:?}", old_coldkey, new_coldkey); Self::do_swap_coldkey(&old_coldkey, &new_coldkey) @@ -931,8 +931,13 @@ mod dispatches { #[pallet::weight((Weight::from_parts(119_000_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] - pub fn dissolve_network(origin: OriginFor, netuid: u16) -> DispatchResult { - Self::user_remove_network(origin, netuid) + pub fn dissolve_network( + origin: OriginFor, + coldkey: T::AccountId, + netuid: u16, + ) -> DispatchResult { + ensure_root(origin)?; + Self::user_remove_network(coldkey, netuid) } /// Set a single child for a given hotkey on a specified network. @@ -1101,7 +1106,10 @@ mod dispatches { let duration: BlockNumberFor = DissolveNetworkScheduleDuration::::get(); let when: BlockNumberFor = current_block.saturating_add(duration); - let call = Call::::dissolve_network { netuid }; + let call = Call::::dissolve_network { + coldkey: who.clone(), + netuid, + }; let bound_call = T::Preimages::bound(LocalCallOf::::from(call.clone())) .map_err(|_| Error::::FailedToSchedule)?; @@ -1110,7 +1118,7 @@ mod dispatches { DispatchTime::At(when), None, 63, - frame_system::RawOrigin::Signed(who.clone()).into(), + frame_system::RawOrigin::Root.into(), bound_call, ) .map_err(|_| Error::::FailedToSchedule)?; diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index b5e34b842..9d8530504 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -196,5 +196,9 @@ mod events { /// extrinsic execution block number execution_block: BlockNumberFor, }, + /// The duration of schedule coldkey swap has been set + ColdkeySwapScheduleDurationSet(BlockNumberFor), + /// The duration of dissolve network has been set + DissolveNetworkScheduleDurationSet(BlockNumberFor), } } diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 0e1038b18..76546a1a2 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -1,6 +1,6 @@ use super::*; use crate::{ - system::{ensure_root, ensure_signed_or_root}, + system::{ensure_root, ensure_signed_or_root, pallet_prelude::BlockNumberFor}, Error, }; use sp_core::Get; @@ -750,4 +750,34 @@ impl Pallet { // Emit an event to notify listeners about the change Self::deposit_event(Event::NetworkMaxStakeSet(netuid, max_stake)); } + + /// 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)); + } + + /// Set the duration for dissolve network + /// + /// # Arguments + /// + /// * `duration` - The blocks for dissolve network execution. + /// + /// # Effects + /// + /// * Update the DissolveNetworkScheduleDuration storage. + /// * Emits a DissolveNetworkScheduleDurationSet evnet. + pub fn set_dissolve_network_schedule_duration(duration: BlockNumberFor) { + DissolveNetworkScheduleDuration::::set(duration); + Self::deposit_event(Event::DissolveNetworkScheduleDurationSet(duration)); + } } diff --git a/pallets/subtensor/tests/networks.rs b/pallets/subtensor/tests/networks.rs index b41ff985d..3d3644236 100644 --- a/pallets/subtensor/tests/networks.rs +++ b/pallets/subtensor/tests/networks.rs @@ -1,7 +1,7 @@ use crate::mock::*; use frame_support::assert_ok; use frame_system::Config; -use pallet_subtensor::{DissolveNetworkScheduleDuration, Event}; +use pallet_subtensor::{ColdkeySwapScheduleDuration, DissolveNetworkScheduleDuration, Event}; use sp_core::U256; mod mock; @@ -35,7 +35,7 @@ fn test_registration_ok() { )); assert_ok!(SubtensorModule::user_remove_network( - <::RuntimeOrigin>::signed(coldkey_account_id), + coldkey_account_id, netuid )); @@ -94,3 +94,191 @@ fn test_schedule_dissolve_network_execution() { assert!(!SubtensorModule::if_subnet_exist(netuid)); }) } + +#[test] +fn test_non_owner_schedule_dissolve_network_execution() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let non_network_owner_account_id = U256::from(2); // + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(non_network_owner_account_id), + netuid + )); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: non_network_owner_account_id, + netuid, + execution_block, + } + .into(), + ); + + run_to_block(execution_block); + // network exists since the caller is no the network owner + assert!(SubtensorModule::if_subnet_exist(netuid)); + }) +} + +#[test] +fn test_new_owner_schedule_dissolve_network_execution() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let new_network_owner_account_id = U256::from(2); // + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + // the account is not network owner when schedule the call + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(new_network_owner_account_id), + netuid + )); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: new_network_owner_account_id, + netuid, + execution_block, + } + .into(), + ); + run_to_block(current_block + 1); + // become network owner after call scheduled + pallet_subtensor::SubnetOwner::::insert(netuid, new_network_owner_account_id); + + run_to_block(execution_block); + // network exists since the caller is no the network owner + assert!(!SubtensorModule::if_subnet_exist(netuid)); + }) +} + +#[test] +fn test_schedule_dissolve_network_execution_with_coldkey_swap() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let new_network_owner_account_id = U256::from(2); // + + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 1000000000000000); + + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + // the account is not network owner when schedule the call + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(coldkey_account_id), + new_network_owner_account_id + )); + + let current_block = System::block_number(); + let execution_block = current_block + ColdkeySwapScheduleDuration::::get(); + + run_to_block(execution_block - 1); + + // the account is not network owner when schedule the call + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(new_network_owner_account_id), + netuid + )); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: new_network_owner_account_id, + netuid, + execution_block: DissolveNetworkScheduleDuration::::get() + execution_block + - 1, + } + .into(), + ); + + run_to_block(execution_block); + assert_eq!( + pallet_subtensor::SubnetOwner::::get(netuid), + new_network_owner_account_id + ); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + run_to_block(execution_block); + // network exists since the caller is no the network owner + assert!(!SubtensorModule::if_subnet_exist(netuid)); + }) +} diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 84df71d83..9b0c76965 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -914,7 +914,8 @@ fn test_dissolve_network_ok() { assert!(SubtensorModule::if_subnet_exist(netuid)); assert_ok!(SubtensorModule::dissolve_network( - RuntimeOrigin::signed(owner_coldkey), + RuntimeOrigin::root(), + owner_coldkey, netuid )); assert!(!SubtensorModule::if_subnet_exist(netuid)) @@ -937,7 +938,8 @@ fn test_dissolve_network_refund_coldkey_ok() { assert!(SubtensorModule::if_subnet_exist(netuid)); assert_ok!(SubtensorModule::dissolve_network( - RuntimeOrigin::signed(owner_coldkey), + RuntimeOrigin::root(), + owner_coldkey, netuid )); assert!(!SubtensorModule::if_subnet_exist(netuid)); @@ -961,7 +963,7 @@ fn test_dissolve_network_not_owner_err() { register_ok_neuron(netuid, hotkey, owner_coldkey, 3); assert_err!( - SubtensorModule::dissolve_network(RuntimeOrigin::signed(random_coldkey), netuid), + SubtensorModule::dissolve_network(RuntimeOrigin::root(), random_coldkey, netuid), Error::::NotSubnetOwner ); }); @@ -974,7 +976,7 @@ fn test_dissolve_network_does_not_exist_err() { let coldkey = U256::from(2); assert_err!( - SubtensorModule::dissolve_network(RuntimeOrigin::signed(coldkey), netuid), + SubtensorModule::dissolve_network(RuntimeOrigin::root(), coldkey, netuid), Error::::SubNetworkDoesNotExist ); });