diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index af9b68051f..9aeb23ee56 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -280,5 +280,18 @@ mod benchmarks { _(RawOrigin::Root, 1u16/*netuid*/, 1_000_000_000_000_000u64/*max_stake*/)/*sudo_set_network_max_stake*/; } + #[benchmark] + fn sudo_enable_alpha_transfer() { + pallet_subtensor::Pallet::::init_new_network( + 1u16, /*netuid*/ + 1u16, /*sudo_tempo*/ + ); + let owner: T::AccountId = account("Alice", 0, 1); + pallet_subtensor::SubnetOwner::::insert(1u16, owner.clone()); + + #[extrinsic_call] + _(RawOrigin::Signed(owner.clone()), 1u16/*netuid*/)/*sudo_enable_alpha_transfer*/; + } + //impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 90e0c12929..2136cbc755 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -69,7 +69,11 @@ pub mod pallet { } #[pallet::event] - pub enum Event {} + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Alpha transfer was enabled on a subnet. + AlphaTransferEnabled(u16, bool), + } // Errors inform users that something went wrong. #[pallet::error] @@ -1287,6 +1291,42 @@ pub mod pallet { ensure_root(origin)?; T::Grandpa::schedule_change(next_authorities, in_blocks, forced) } + + /// Enables alpha transfer for a specific subnet. + /// + /// This extrinsic allows the subnet owner to enable alpha transfer for a specific subnet. + /// + /// # Arguments + /// * `origin` - The origin of the call, which must be the subnet owner. + /// * `netuid` - The unique identifier of the subnet for which the periods are being set. + /// + /// # Errors + /// * `BadOrigin` - If the caller is neither the subnet owner nor the root account. + /// * `SubnetDoesNotExist` - If the specified subnet does not exist. + /// + /// # Weight + /// Weight is handled by the `#[pallet::weight]` attribute. + #[pallet::call_index(61)] + #[pallet::weight(::WeightInfo::sudo_enable_alpha_transfer())] + pub fn sudo_enable_alpha_transfer(origin: OriginFor, netuid: u16) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner(origin, netuid)?; + + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + + if pallet_subtensor::Pallet::::get_alpha_transfer_enabled(netuid) { + return Ok(()); // exit early if already enabled + } + + pallet_subtensor::Pallet::::set_alpha_transfer_enabled(netuid, true); + // Log and emit event + log::debug!("AlphaTransferEnabled( netuid: {:?} ) ", netuid); + Self::deposit_event(Event::AlphaTransferEnabled(netuid, true)); + + Ok(()) + } } } diff --git a/pallets/admin-utils/src/weights.rs b/pallets/admin-utils/src/weights.rs index 6ef9523546..6d0852f566 100644 --- a/pallets/admin-utils/src/weights.rs +++ b/pallets/admin-utils/src/weights.rs @@ -65,6 +65,7 @@ pub trait WeightInfo { fn sudo_set_commit_reveal_weights_enabled() -> Weight; fn sudo_set_evm_chain_id() -> Weight; fn schedule_grandpa_change(a: u32) -> Weight; + fn sudo_enable_alpha_transfer() -> Weight; } /// Weights for `pallet_admin_utils` using the Substrate node and recommended hardware. @@ -456,6 +457,14 @@ impl WeightInfo for SubstrateWeight { // TODO should be replaced by benchmarked weights Weight::default() } + + fn sudo_enable_alpha_transfer() -> Weight { + // TODO benchmark + // 1 read for check subnet owner + // 1 read for check alpha transfer enabled + // 1 write for set alpha transfer enabled + Weight::default().saturating_add(RocksDbWeight::get().reads_writes(2_u64, 1_u64)) + } } // For backwards compatibility and tests. @@ -851,4 +860,11 @@ impl WeightInfo for () { // TODO should be replaced by benchmarked weights Weight::default() } + fn sudo_enable_alpha_transfer() -> Weight { + // TODO benchmark + // 1 read for check subnet owner + // 1 read for check alpha transfer enabled + // 1 write for set alpha transfer enabled + Weight::default().saturating_add(RocksDbWeight::get().reads_writes(2_u64, 1_u64)) + } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 7f9cd7120d..b1e9cbcc42 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -669,6 +669,11 @@ pub mod pallet { false } #[pallet::type_value] + /// Default value for alpha transfer enabled. + pub fn DefaultAlphaTransferEnabled() -> bool { + false + } + #[pallet::type_value] /// Senate requirements pub fn DefaultSenateRequiredStakePercentage() -> u64 { T::InitialSenateRequiredStakePercentage::get() @@ -1227,6 +1232,10 @@ pub mod pallet { pub type CommitRevealWeightsEnabled = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultCommitRevealWeightsEnabled>; #[pallet::storage] + /// --- MAP ( netuid ) --> alpha transfer enabled if true + pub type AlphaTransferEnabled = + StorageMap<_, Identity, u16, bool, ValueQuery, DefaultAlphaTransferEnabled>; + #[pallet::storage] /// --- MAP ( netuid ) --> Burn pub type Burn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBurn>; #[pallet::storage] @@ -1570,6 +1579,7 @@ pub enum CustomTransactionError { RateLimitExceeded, InsufficientLiquidity, BadRequest, + AlphaTransferNotEnabled, } impl From for u8 { @@ -1583,6 +1593,7 @@ impl From for u8 { CustomTransactionError::NotEnoughStakeToWithdraw => 5, CustomTransactionError::RateLimitExceeded => 6, CustomTransactionError::InsufficientLiquidity => 7, + CustomTransactionError::AlphaTransferNotEnabled => 8, CustomTransactionError::BadRequest => 255, } } @@ -1654,6 +1665,10 @@ where CustomTransactionError::InsufficientLiquidity.into(), ) .into()), + Error::::AlphaTransferNotEnabled => Err(InvalidTransaction::Custom( + CustomTransactionError::AlphaTransferNotEnabled.into(), + ) + .into()), _ => Err( InvalidTransaction::Custom(CustomTransactionError::BadRequest.into()).into(), ), @@ -1842,7 +1857,7 @@ where alpha_amount, }) => { // Fully validate the user input - Self::result_to_validity(Pallet::::validate_stake_transition( + Self::result_to_validity(Pallet::::validate_alpha_transfer( who, destination_coldkey, hotkey, diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index f1eea3b9c6..d173775f9a 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -185,6 +185,8 @@ mod errors { CommittingWeightsTooFast, /// Stake amount is too low. AmountTooLow, + /// Alpha transfer is not enabled on this subnet. + AlphaTransferNotEnabled, /// Not enough liquidity. InsufficientLiquidity, } diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 75f9331356..6a0c9bf034 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -115,6 +115,12 @@ impl Pallet { alpha_amount, )?; + // Ensure alpha transfer is enabled on the origin subnet + ensure!( + Pallet::::get_alpha_transfer_enabled(origin_netuid), + Error::::AlphaTransferNotEnabled + ); + // 9. Emit an event for logging/monitoring. log::info!( "StakeTransferred(origin_coldkey: {:?}, destination_coldkey: {:?}, hotkey: {:?}, origin_netuid: {:?}, destination_netuid: {:?}, amount: {:?})", diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 805b61e50d..65e3b78523 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -856,6 +856,31 @@ impl Pallet { Ok(()) } + + pub fn validate_alpha_transfer( + origin_coldkey: &T::AccountId, + destination_coldkey: &T::AccountId, + origin_hotkey: &T::AccountId, + destination_hotkey: &T::AccountId, + origin_netuid: u16, + destination_netuid: u16, + alpha_amount: u64, + ) -> Result<(), Error> { + ensure!( + Self::get_alpha_transfer_enabled(origin_netuid), + Error::::AlphaTransferNotEnabled + ); + + Self::validate_stake_transition( + origin_coldkey, + destination_coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ) + } } /////////////////////////////////////////// diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index 7a60d47367..ba7fb788cd 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -829,6 +829,9 @@ fn test_do_transfer_success() { let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let fee = DefaultStakingFee::::get(); + // Enable alpha transfer on this subnet + SubtensorModule::set_alpha_transfer_enabled(netuid, true); + // 2. Define the origin coldkey, destination coldkey, and hotkey to be used. let origin_coldkey = U256::from(1); let destination_coldkey = U256::from(2); @@ -906,6 +909,9 @@ fn test_do_transfer_nonexistent_hotkey() { let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + // Enable alpha transfer on this subnet + SubtensorModule::set_alpha_transfer_enabled(netuid, true); + let origin_coldkey = U256::from(1); let destination_coldkey = U256::from(2); let nonexistent_hotkey = U256::from(999); @@ -931,6 +937,9 @@ fn test_do_transfer_insufficient_stake() { let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + // Enable alpha transfer on this subnet + SubtensorModule::set_alpha_transfer_enabled(netuid, true); + let origin_coldkey = U256::from(1); let destination_coldkey = U256::from(2); let hotkey = U256::from(3); @@ -961,6 +970,9 @@ fn test_do_transfer_wrong_origin() { let subnet_owner_hotkey = U256::from(1011); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + // Enable alpha transfer on this subnet + SubtensorModule::set_alpha_transfer_enabled(netuid, true); + let origin_coldkey = U256::from(1); let wrong_coldkey = U256::from(9999); let destination_coldkey = U256::from(2); @@ -993,6 +1005,9 @@ fn test_do_transfer_minimum_stake_check() { let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + // Enable alpha transfer on this subnet + SubtensorModule::set_alpha_transfer_enabled(netuid, true); + let origin_coldkey = U256::from(1); let destination_coldkey = U256::from(2); let hotkey = U256::from(3); @@ -1024,6 +1039,9 @@ fn test_do_transfer_different_subnets() { let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + // Enable alpha transfer on origin subnet + SubtensorModule::set_alpha_transfer_enabled(origin_netuid, true); + // 2. Define origin/destination coldkeys and hotkey. let origin_coldkey = U256::from(1); let destination_coldkey = U256::from(2); @@ -1088,6 +1106,40 @@ fn test_do_transfer_different_subnets() { }); } +/// RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::move_stake::test_do_transfer_alpha_transfer_not_enabled --show-output +#[test] +fn test_do_transfer_alpha_transfer_not_enabled() { + 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 origin_coldkey = U256::from(1); + let destination_coldkey = U256::from(2); + let hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount, 0); + + // Verify alpha transfer is not enabled + assert!(!SubtensorModule::get_alpha_transfer_enabled(netuid)); + + let alpha = stake_amount - 10_000; + assert_err!( + SubtensorModule::do_transfer_stake( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + hotkey, + netuid, + netuid, + alpha + ), + Error::::AlphaTransferNotEnabled + ); + }); +} + #[test] fn test_do_swap_success() { new_test_ext(1).execute_with(|| { diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index b27921f27c..2b90401436 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, pallet_prelude::BlockNumberFor}, + system::{ensure_root, ensure_signed, ensure_signed_or_root, pallet_prelude::BlockNumberFor}, Error, }; use safe_math::*; @@ -23,6 +23,17 @@ impl Pallet { } } + /// Ensure that the caller is the owner of the subnet. + /// Note: this is *not* true for the root account. + pub fn ensure_subnet_owner(o: T::RuntimeOrigin, netuid: u16) -> Result<(), DispatchError> { + let coldkey = ensure_signed(o); + match coldkey { + Ok(who) if SubnetOwner::::get(netuid) == who => Ok(()), + Ok(_) => Err(DispatchError::BadOrigin), + Err(x) => Err(x.into()), + } + } + // ======================== // ==== Global Setters ==== // ======================== @@ -471,6 +482,13 @@ impl Pallet { CommitRevealWeightsEnabled::::set(netuid, enabled); } + pub fn get_alpha_transfer_enabled(netuid: u16) -> bool { + AlphaTransferEnabled::::get(netuid) + } + pub fn set_alpha_transfer_enabled(netuid: u16, enabled: bool) { + AlphaTransferEnabled::::set(netuid, enabled); + } + pub fn get_rho(netuid: u16) -> u16 { Rho::::get(netuid) }