diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 03c35ccea5..ec633178bd 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1149,6 +1149,45 @@ pub mod pallet { Ok(()) } + /// The extrinsic sets the minimum childkey take for a subnet. + /// It is callable by root or the subnet owner. + /// The subnet minimum can only make the global minimum stricter. + #[pallet::call_index(93)] + #[pallet::weight(::WeightInfo::sudo_set_min_childkey_take_per_subnet())] + pub fn sudo_set_min_childkey_take_per_subnet( + origin: OriginFor, + netuid: NetUid, + take: u16, + ) -> DispatchResult { + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[Hyperparameter::MinChildkeyTake.into()], + )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; + + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + ensure!( + take >= pallet_subtensor::Pallet::::get_min_childkey_take() + && take <= pallet_subtensor::Pallet::::get_max_childkey_take(), + Error::::InvalidValue + ); + + pallet_subtensor::Pallet::::set_min_childkey_take_for_subnet(netuid, take); + pallet_subtensor::Pallet::::record_owner_rl( + maybe_owner, + netuid, + &[Hyperparameter::MinChildkeyTake.into()], + ); + log::debug!( + "MinChildkeyTakePerSubnetSet( netuid: {netuid:?}, min_childkey_take: {take:?} ) " + ); + Ok(()) + } + /// The extrinsic enabled/disables commit/reaveal for a given subnet. /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the value. diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index c94e1e96e8..9d06bfd0cd 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1107,6 +1107,67 @@ fn test_sudo_set_min_delegate_take() { }); } +#[test] +fn test_sudo_set_min_childkey_take_per_subnet() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + let owner = U256::from(10); + let non_owner = U256::from(11); + let take = SubtensorModule::get_max_childkey_take() / 2; + + add_network(netuid, 10); + SubnetOwner::::insert(netuid, owner); + + assert_eq!( + AdminUtils::sudo_set_min_childkey_take_per_subnet( + <::RuntimeOrigin>::signed(non_owner), + netuid, + take + ), + Err(DispatchError::BadOrigin) + ); + + assert_ok!(AdminUtils::sudo_set_min_childkey_take_per_subnet( + <::RuntimeOrigin>::signed(owner), + netuid, + take + )); + assert_eq!( + SubtensorModule::get_min_childkey_take_for_subnet(netuid), + take + ); + assert_eq!( + SubtensorModule::get_effective_min_childkey_take(netuid), + take + ); + }); +} + +#[test] +fn test_sudo_set_min_childkey_take_per_subnet_rejects_below_global() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + let global_min = 100; + + add_network(netuid, 10); + SubtensorModule::set_min_childkey_take(global_min); + + assert_noop!( + AdminUtils::sudo_set_min_childkey_take_per_subnet( + <::RuntimeOrigin>::root(), + netuid, + global_min - 1 + ), + Error::::InvalidValue + ); + assert_ok!(AdminUtils::sudo_set_min_childkey_take_per_subnet( + <::RuntimeOrigin>::root(), + netuid, + global_min + )); + }); +} + #[test] fn test_sudo_set_commit_reveal_weights_enabled() { new_test_ext().execute_with(|| { diff --git a/pallets/admin-utils/src/weights.rs b/pallets/admin-utils/src/weights.rs index 312d1b19c9..887cf2f247 100644 --- a/pallets/admin-utils/src/weights.rs +++ b/pallets/admin-utils/src/weights.rs @@ -72,6 +72,7 @@ pub trait WeightInfo { fn sudo_set_nominator_min_required_stake() -> Weight; fn sudo_set_tx_delegate_take_rate_limit() -> Weight; fn sudo_set_min_delegate_take() -> Weight; + fn sudo_set_min_childkey_take_per_subnet() -> Weight; fn sudo_set_liquid_alpha_enabled() -> Weight; fn sudo_set_alpha_values() -> Weight; fn sudo_set_coldkey_swap_announcement_delay() -> Weight; @@ -641,6 +642,15 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(5_410_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::MinChildkeyTake` (r:1 w:0) + /// Proof: `SubtensorModule::MinChildkeyTake` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::MinChildkeyTakePerSubnet` (r:0 w:1) + /// Proof: `SubtensorModule::MinChildkeyTakePerSubnet` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn sudo_set_min_childkey_take_per_subnet() -> Weight { + Weight::from_parts(6_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } /// Storage: `SubtensorModule::Tempo` (r:1 w:0) /// Proof: `SubtensorModule::Tempo` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::AdminFreezeWindow` (r:1 w:0) @@ -1456,6 +1466,15 @@ impl WeightInfo for () { Weight::from_parts(5_410_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::MinChildkeyTake` (r:1 w:0) + /// Proof: `SubtensorModule::MinChildkeyTake` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::MinChildkeyTakePerSubnet` (r:0 w:1) + /// Proof: `SubtensorModule::MinChildkeyTakePerSubnet` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn sudo_set_min_childkey_take_per_subnet() -> Weight { + Weight::from_parts(6_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } /// Storage: `SubtensorModule::Tempo` (r:1 w:0) /// Proof: `SubtensorModule::Tempo` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::AdminFreezeWindow` (r:1 w:0) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 98ee408fec..54205dc445 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1170,6 +1170,10 @@ pub mod pallet { #[pallet::storage] pub type MinChildkeyTake = StorageValue<_, u16, ValueQuery, DefaultMinChildKeyTake>; + /// MAP ( netuid ) --> take | Returns the subnet-specific minimum childkey take. + #[pallet::storage] + pub type MinChildkeyTakePerSubnet = StorageMap<_, Identity, NetUid, u16, ValueQuery>; + /// MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey #[pallet::storage] pub type Owner = diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index cdb37bb0dd..a26549ada2 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -121,6 +121,8 @@ mod events { OwnerHyperparamRateLimitSet(u16), /// minimum childkey take set MinChildKeyTakeSet(u16), + /// subnet-specific minimum childkey take set + MinChildKeyTakePerSubnetSet(NetUid, u16), /// maximum childkey take set MaxChildKeyTakeSet(u16), /// childkey take set diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 9b63024c17..2bd2cf1b95 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -735,7 +735,8 @@ impl Pallet { // Ensure the take value is valid ensure!( - take <= Self::get_max_childkey_take(), + take >= Self::get_effective_min_childkey_take(netuid) + && take <= Self::get_max_childkey_take(), Error::::InvalidChildkeyTake ); @@ -789,7 +790,7 @@ impl Pallet { /// - The childkey take value. This is a percentage represented as a value between 0 /// and 10000, where 10000 represents 100%. pub fn get_childkey_take(hotkey: &T::AccountId, netuid: NetUid) -> u16 { - ChildkeyTake::::get(hotkey, netuid) + ChildkeyTake::::get(hotkey, netuid).max(Self::get_effective_min_childkey_take(netuid)) } pub fn get_auto_parent_delegation_enabled(root_validator_hotkey: &T::AccountId) -> bool { diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index a7d4b1b273..ec191ba0e7 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -924,6 +924,52 @@ fn test_childkey_take_functionality() { }); } +#[test] +fn test_childkey_take_respects_effective_subnet_minimum() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid = NetUid::from(1); + let subnet_min = SubtensorModule::get_max_childkey_take() / 2; + + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + SubtensorModule::set_min_childkey_take_for_subnet(netuid, subnet_min); + + assert_eq!( + SubtensorModule::get_effective_min_childkey_take(netuid), + subnet_min + ); + assert_eq!( + SubtensorModule::get_childkey_take(&hotkey, netuid), + subnet_min + ); + + assert_noop!( + SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + subnet_min - 1 + ), + Error::::InvalidChildkeyTake + ); + + assert_ok!(SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + subnet_min + )); + + ChildkeyTake::::insert(hotkey, netuid, subnet_min - 1); + assert_eq!( + SubtensorModule::get_childkey_take(&hotkey, netuid), + subnet_min + ); + }); +} + // 25: Test childkey take rate limiting // This test verifies the rate limiting functionality for setting childkey take: // - Sets up a network and registers a hotkey diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index f6b24db36b..a1c0309b24 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -408,6 +408,10 @@ impl Pallet { MinChildkeyTake::::put(take); Self::deposit_event(Event::MinChildKeyTakeSet(take)); } + pub fn set_min_childkey_take_for_subnet(netuid: NetUid, take: u16) { + MinChildkeyTakePerSubnet::::insert(netuid, take); + Self::deposit_event(Event::MinChildKeyTakePerSubnetSet(netuid, take)); + } pub fn set_max_childkey_take(take: u16) { MaxChildkeyTake::::put(take); Self::deposit_event(Event::MaxChildKeyTakeSet(take)); @@ -415,6 +419,12 @@ impl Pallet { pub fn get_min_childkey_take() -> u16 { MinChildkeyTake::::get() } + pub fn get_min_childkey_take_for_subnet(netuid: NetUid) -> u16 { + MinChildkeyTakePerSubnet::::get(netuid) + } + pub fn get_effective_min_childkey_take(netuid: NetUid) -> u16 { + Self::get_min_childkey_take().max(Self::get_min_childkey_take_for_subnet(netuid)) + } pub fn get_max_childkey_take() -> u16 { MaxChildkeyTake::::get() diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index 602600e6cc..e9559f2c6d 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -205,6 +205,7 @@ pub enum Hyperparameter { BurnHalfLife = 26, BurnIncreaseMult = 27, SubnetEmissionEnabled = 28, + MinChildkeyTake = 29, } impl Pallet { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 15d8a43bef..f5cb9e2c56 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -815,6 +815,9 @@ impl InstanceFilter for ProxyType { | RuntimeCall::AdminUtils( pallet_admin_utils::Call::sudo_set_subnet_emission_enabled { .. } ) + | RuntimeCall::AdminUtils( + pallet_admin_utils::Call::sudo_set_min_childkey_take_per_subnet { .. } + ) ), ProxyType::RootClaim => matches!( c,