diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 4688b1f22f..395d6712b2 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -2141,6 +2141,36 @@ pub mod pallet { Ok(()) } + + /// Set whether the subnet owner cut is enabled for a subnet. + /// It is only callable by root and subnet owner. + #[pallet::call_index(92)] + #[pallet::weight(( + Weight::from_parts(25_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Operational, + Pays::Yes, + ))] + pub fn sudo_set_owner_cut_enabled( + origin: OriginFor, + netuid: NetUid, + enabled: bool, + ) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; + + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + ensure!(!netuid.is_root(), Error::::NotPermittedOnRootSubnet); + + pallet_subtensor::Pallet::::set_owner_cut_enabled_flag(netuid, enabled); + log::debug!("OwnerCutEnabledSet( netuid: {netuid:?}, enabled: {enabled:?} ) "); + + Ok(()) + } } } diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index c94e1e96e8..b35c45f00f 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2358,6 +2358,44 @@ fn test_sudo_set_mechanism_count() { }); } +#[test] +fn test_sudo_set_owner_cut_enabled() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(11); + let owner = U256::from(1234); + let call = RuntimeCall::AdminUtils(crate::Call::sudo_set_owner_cut_enabled { + netuid, + enabled: false, + }); + + add_network(netuid, 10); + SubnetOwner::::insert(netuid, owner); + + assert_ok!(AdminUtils::sudo_set_admin_freeze_window( + <::RuntimeOrigin>::root(), + 0 + )); + + let dispatch_info = call.get_dispatch_info(); + assert_eq!(dispatch_info.pays_fee, Pays::Yes); + + assert!(SubtensorModule::get_owner_cut_enabled(netuid)); + assert_ok!(AdminUtils::sudo_set_owner_cut_enabled( + <::RuntimeOrigin>::signed(owner), + netuid, + false + )); + assert!(!SubtensorModule::get_owner_cut_enabled(netuid)); + + assert_ok!(AdminUtils::sudo_set_owner_cut_enabled( + <::RuntimeOrigin>::root(), + netuid, + true + )); + assert!(SubtensorModule::get_owner_cut_enabled(netuid)); + }); +} + // cargo test --package pallet-admin-utils --lib -- tests::test_sudo_set_mechanism_count_and_emissions --exact --show-output #[test] fn test_sudo_set_mechanism_count_and_emissions() { diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 2854777abc..68d4b25176 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -258,14 +258,16 @@ impl Pallet { Self::resolve_to_alpha_out(Self::mint_alpha(*netuid_i, alpha_created)); // Calculate the owner cut. - let owner_cut_i: U96F32 = alpha_out_i.saturating_mul(cut_percent); - log::debug!("owner_cut_i: {owner_cut_i:?}"); - // Deduct owner cut from alpha_out. - alpha_out_i = alpha_out_i.saturating_sub(owner_cut_i); - // Accumulate the owner cut in pending. - PendingOwnerCut::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(owner_cut_i).into()); - }); + if Self::get_owner_cut_enabled(*netuid_i) { + let owner_cut_i: U96F32 = alpha_out_i.saturating_mul(cut_percent); + log::debug!("owner_cut_i: {owner_cut_i:?}"); + // Deduct owner cut from alpha_out. + alpha_out_i = alpha_out_i.saturating_sub(owner_cut_i); + // Accumulate the owner cut in pending. + PendingOwnerCut::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(owner_cut_i).into()); + }); + } // Get root proportional dividends. let root_proportion = Self::root_proportion(*netuid_i); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 75735c7471..127d4f823d 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1685,6 +1685,17 @@ pub mod pallet { #[pallet::storage] pub type SubnetOwnerCut = StorageValue<_, u16, ValueQuery, DefaultSubnetOwnerCut>; + /// Default value for subnet owner cut enabled flag. + #[pallet::type_value] + pub fn DefaultOwnerCutEnabled() -> bool { + true + } + + /// --- MAP ( netuid ) --> owner_cut_enabled + #[pallet::storage] + pub type OwnerCutEnabled = + StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultOwnerCutEnabled>; + /// ITEM( network_rate_limit ) #[pallet::storage] pub type NetworkRateLimit = StorageValue<_, u64, ValueQuery, DefaultNetworkRateLimit>; diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index e1aa5eb744..d8d27760d4 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -462,4 +462,20 @@ impl Pallet { _ => None, } } + + /// Returns whether the owner cut is enabled for the given subnet. + /// + /// Returns `true` if the owner cut is enabled for the subnet, otherwise `false`. + pub fn get_owner_cut_enabled(netuid: NetUid) -> bool { + OwnerCutEnabled::::get(netuid) + } + + /// Sets whether the owner cut is enabled for the given subnet. + /// + /// # Parameters + /// - `netuid`: The identifier of the subnet to update. + /// - `value`: `true` to enable the owner cut for the subnet, `false` to disable it. + pub fn set_owner_cut_enabled_flag(netuid: NetUid, value: bool) { + OwnerCutEnabled::::insert(netuid, value); + } } diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 6199aa9952..216fa09c41 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -3834,6 +3834,123 @@ fn test_coinbase_emit_to_subnets_with_root_sell() { }); } +// cargo test --package pallet-subtensor --lib -- tests::coinbase::test_disabling_owner_cut_sends_subnet_emission_to_miners_and_validators --exact --nocapture +#[test] +fn test_disabling_owner_cut_sends_subnet_emission_to_miners_and_validators() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let validator_coldkey = U256::from(1); + let validator_hotkey = U256::from(2); + let miner_coldkey = U256::from(5); + let miner_hotkey = U256::from(6); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let subnet_tempo = 10; + let stake = 100_000_000_000u64; + + SubtensorModule::set_tempo(netuid, subnet_tempo); + setup_reserves(netuid, (stake * 10_000).into(), (stake * 10_000).into()); + + register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); + register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 1); + + add_balance_to_coldkey_account( + &validator_coldkey, + TaoBalance::from(stake) + ExistentialDeposit::get(), + ); + + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + netuid, + stake.into() + )); + + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_max_allowed_validators(netuid, 1); + step_block(subnet_tempo); + + SubnetOwnerCut::::set(u16::MAX / 10); + SubtensorModule::set_owner_cut_enabled_flag(netuid, false); + + let owner_uid = + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &subnet_owner_hotkey).unwrap(); + let validator_uid = + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &validator_hotkey).unwrap(); + let miner_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &miner_hotkey).unwrap(); + let uid_count = [ + owner_uid as usize, + validator_uid as usize, + miner_uid as usize, + ] + .into_iter() + .max() + .unwrap() + + 1; + + Weights::::insert( + NetUidStorageIndex::from(netuid), + validator_uid, + vec![(miner_uid, 0xFFFF)], + ); + BlockAtRegistration::::set(netuid, owner_uid, 1); + BlockAtRegistration::::set(netuid, validator_uid, 1); + BlockAtRegistration::::set(netuid, miner_uid, 1); + LastUpdate::::set(NetUidStorageIndex::from(netuid), vec![2; uid_count]); + Kappa::::set(netuid, u16::MAX / 5); + ActivityCutoff::::set(netuid, u16::MAX); + let mut validator_permit = vec![false; uid_count]; + validator_permit[validator_uid as usize] = true; + ValidatorPermit::::insert(netuid, validator_permit); + + let owner_stake_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &subnet_owner_hotkey, + &subnet_owner_coldkey, + netuid, + ); + let validator_stake_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &validator_hotkey, + &validator_coldkey, + netuid, + ); + let miner_stake_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ); + + // Disabling owner cut removes the subnet owner from emission distribution, so the + // subnet emission is fully distributed across the validator and miner paths instead. + step_block(subnet_tempo); + + let owner_stake_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &subnet_owner_hotkey, + &subnet_owner_coldkey, + netuid, + ); + let validator_stake_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &validator_hotkey, + &validator_coldkey, + netuid, + ); + let miner_stake_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ); + + assert_eq!(owner_stake_after, owner_stake_before); + assert!(validator_stake_after > validator_stake_before); + assert!(miner_stake_after > miner_stake_before); + assert_eq!(PendingOwnerCut::::get(netuid), AlphaBalance::ZERO); + assert!( + Lock::::iter_prefix((subnet_owner_coldkey, netuid)) + .next() + .is_none() + ); + }); +} + // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_pending_emission_start_call_not_done --exact --show-output --nocapture #[test] fn test_pending_emission_start_call_not_done() {