diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 85c7ef62c..98c8dde6f 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -707,10 +707,15 @@ pub mod pallet { ))] pub fn sudo_set_subnet_owner_cut( origin: OriginFor, + netuid: u16, subnet_owner_cut: u16, ) -> DispatchResult { ensure_root(origin)?; - pallet_subtensor::Pallet::::set_subnet_owner_cut(subnet_owner_cut); + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + pallet_subtensor::Pallet::::set_subnet_owner_cut(netuid, subnet_owner_cut); log::debug!( "SubnetOwnerCut( subnet_owner_cut: {:?} ) ", subnet_owner_cut @@ -718,6 +723,57 @@ pub mod pallet { Ok(()) } + /// The extrinsic sets the subnet miner cut for a subnet. + /// It is only callable by the root account. + /// The extrinsic will call the Subtensor pallet to set the subnet miner cut. + #[pallet::call_index(58)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_subnet_miner_cut( + origin: OriginFor, + netuid: u16, + subnet_miner_cut: u16, + ) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + + match pallet_subtensor::Pallet::::ensure_subnet_miner_cut(netuid, subnet_miner_cut) + { + Ok(cut) => pallet_subtensor::Pallet::::set_subnet_burn_cut(netuid, cut), + Err(_) => return Ok(()), + }; + pallet_subtensor::Pallet::::set_subnet_miner_cut(netuid, subnet_miner_cut); + Ok(()) + } + + /// The extrinsic sets the subnet validator cut for a subnet. + /// It is only callable by the root account. + /// The extrinsic will call the Subtensor pallet to set the subnet validator cut. + #[pallet::call_index(59)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_subnet_validator_cut( + origin: OriginFor, + netuid: u16, + subnet_validator_cut: u16, + ) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + match pallet_subtensor::Pallet::::ensure_subnet_miner_cut( + netuid, + subnet_validator_cut, + ) { + Ok(cut) => pallet_subtensor::Pallet::::set_subnet_burn_cut(netuid, cut), + Err(_) => return Ok(()), + }; + pallet_subtensor::Pallet::::set_subnet_validator_cut(netuid, subnet_validator_cut); + Ok(()) + } + /// The extrinsic sets the network rate limit for the network. /// It is only callable by the root account. /// The extrinsic will call the Subtensor pallet to set the network rate limit. diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index dca08ab72..8749a8e5c 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -112,7 +112,10 @@ parameter_types! { pub const InitialNetworkImmunityPeriod: u64 = 7200 * 7; pub const InitialNetworkMinAllowedUids: u16 = 128; pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; - pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. + pub const InitialSubnetOwnerCut: u16 = 11_796; // 18% + pub const InitialSubnetMinerCut: u16 = 30_146; // 41% + pub const InitialSubnetValidatorCut: u16 = 30_146; // 41% + pub const InitialSubnetBurnCut: u16 = 0; // 0% pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; @@ -177,6 +180,9 @@ impl pallet_subtensor::Config for Test { type InitialNetworkMinAllowedUids = InitialNetworkMinAllowedUids; type InitialNetworkMinLockCost = InitialNetworkMinLockCost; type InitialSubnetOwnerCut = InitialSubnetOwnerCut; + type InitialSubnetMinerCut = InitialSubnetMinerCut; + type InitialSubnetValidatorCut = InitialSubnetValidatorCut; + type InitialSubnetBurnCut = InitialSubnetBurnCut; type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index 442275052..a7e21d653 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -265,20 +265,129 @@ fn test_sudo_set_adjustment_alpha() { fn test_sudo_subnet_owner_cut() { new_test_ext().execute_with(|| { let to_be_set: u16 = 10; - let init_value: u16 = SubtensorModule::get_subnet_owner_cut(); + let netuid: u16 = 1; + add_network(netuid, 10); + let init_value: u16 = SubtensorModule::get_subnet_owner_cut(netuid); assert_eq!( AdminUtils::sudo_set_subnet_owner_cut( <::RuntimeOrigin>::signed(U256::from(0)), + netuid, to_be_set ), Err(DispatchError::BadOrigin) ); - assert_eq!(SubtensorModule::get_subnet_owner_cut(), init_value); + assert_eq!(SubtensorModule::get_subnet_owner_cut(netuid), init_value); assert_ok!(AdminUtils::sudo_set_subnet_owner_cut( <::RuntimeOrigin>::root(), + netuid, + to_be_set + )); + assert_eq!(SubtensorModule::get_subnet_owner_cut(netuid), to_be_set); + }); +} + +#[test] +fn test_sudo_subnet_miner_cut() { + new_test_ext().execute_with(|| { + let to_be_set: u16 = 1000; + let netuid: u16 = 1; + add_network(netuid, 10); + + let init_value: u16 = SubtensorModule::get_subnet_miner_cut(netuid); + + // Test non-root access (should fail) + assert_eq!( + AdminUtils::sudo_set_subnet_miner_cut( + <::RuntimeOrigin>::signed(U256::from(1)), + netuid, + to_be_set + ), + Err(DispatchError::BadOrigin) + ); + + // Test non-existent subnet (should fail) + assert_eq!( + AdminUtils::sudo_set_subnet_miner_cut( + <::RuntimeOrigin>::root(), + netuid + 1, + to_be_set + ), + Err(Error::::SubnetDoesNotExist.into()) + ); + + // Verify value hasn't changed + assert_eq!(SubtensorModule::get_subnet_miner_cut(netuid), init_value); + + // Test successful update + assert_ok!(AdminUtils::sudo_set_subnet_miner_cut( + <::RuntimeOrigin>::root(), + netuid, + to_be_set + )); + + // Verify new value + assert_eq!(SubtensorModule::get_subnet_miner_cut(netuid), to_be_set); + + let sum = SubtensorModule::get_subnet_owner_cut(netuid) + + SubtensorModule::get_subnet_miner_cut(netuid) + + SubtensorModule::get_subnet_validator_cut(netuid) + + SubtensorModule::get_subnet_burn_cut(netuid); + + // Verify total cut sum is 100% + assert_eq!(sum, u16::MAX); + }); +} + +#[test] +fn test_sudo_subnet_validator_cut() { + new_test_ext().execute_with(|| { + let to_be_set: u16 = 10; + let netuid: u16 = 1; + add_network(netuid, 10); + let init_value: u16 = SubtensorModule::get_subnet_validator_cut(netuid); + + // Test non-root access (should fail) + assert_eq!( + AdminUtils::sudo_set_subnet_validator_cut( + <::RuntimeOrigin>::signed(U256::from(1)), + netuid, + to_be_set + ), + Err(DispatchError::BadOrigin) + ); + + // Test non-existent subnet (should fail) + assert_eq!( + AdminUtils::sudo_set_subnet_validator_cut( + <::RuntimeOrigin>::root(), + netuid + 1, + to_be_set + ), + Err(Error::::SubnetDoesNotExist.into()) + ); + + // Verify value hasn't changed + assert_eq!( + SubtensorModule::get_subnet_validator_cut(netuid), + init_value + ); + + // Test successful update + assert_ok!(AdminUtils::sudo_set_subnet_validator_cut( + <::RuntimeOrigin>::root(), + netuid, to_be_set )); - assert_eq!(SubtensorModule::get_subnet_owner_cut(), to_be_set); + + // Verify new value + assert_eq!(SubtensorModule::get_subnet_validator_cut(netuid), to_be_set); + + let sum = SubtensorModule::get_subnet_owner_cut(netuid) + + SubtensorModule::get_subnet_miner_cut(netuid) + + SubtensorModule::get_subnet_validator_cut(netuid) + + SubtensorModule::get_subnet_burn_cut(netuid); + // Verify total cut sum is 100% + assert_eq!(sum, u16::MAX); }); } diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index badb811fa..3c0003f30 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -93,13 +93,21 @@ impl Pallet { // --- 4.4.1 Compute the subnet owner cut. let owner_cut: I96F32 = I96F32::from_num(subnet_emission).saturating_mul( - I96F32::from_num(Self::get_subnet_owner_cut()) + I96F32::from_num(Self::get_subnet_owner_cut(*netuid)) .saturating_div(I96F32::from_num(u16::MAX)), ); // --- 4.4.2 Remove the cut from the subnet emission subnet_emission = subnet_emission.saturating_sub(owner_cut.to_num::()); + let subnet_burn_cut: I96F32 = I96F32::from_num(subnet_emission).saturating_mul( + I96F32::from_num(Self::get_subnet_burn_cut(*netuid)) + .saturating_div(I96F32::from_num(u16::MAX)), + ); + subnet_emission = + subnet_emission.saturating_sub(subnet_burn_cut.to_num::()); + Self::burn_tokens(subnet_burn_cut.to_num::()); + // --- 4.4.3 Add the cut to the balance of the owner Self::add_balance_to_coldkey_account( &Self::get_subnet_owner(*netuid), diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index d919c6dbb..e6e732071 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -303,10 +303,19 @@ impl Pallet { // Compute rao based emission scores. range: I96F32(0, rao_emission) let float_rao_emission: I96F32 = I96F32::from_num(rao_emission); + // Apply server and validator cuts to their respective emissions. + let server_cut: I96F32 = I96F32::from_num(Self::get_subnet_miner_cut(netuid)) + .saturating_div(I96F32::from_num(u16::MAX)); + let validator_cut: I96F32 = I96F32::from_num(Self::get_subnet_validator_cut(netuid)) + .saturating_div(I96F32::from_num(u16::MAX)); let server_emission: Vec = normalized_server_emission .iter() - .map(|se: &I32F32| I96F32::from_num(*se).saturating_mul(float_rao_emission)) + .map(|se: &I32F32| { + I96F32::from_num(*se) + .saturating_mul(float_rao_emission) + .saturating_mul(server_cut) + }) .collect(); let server_emission: Vec = server_emission .iter() @@ -315,13 +324,16 @@ impl Pallet { let validator_emission: Vec = normalized_validator_emission .iter() - .map(|ve: &I32F32| I96F32::from_num(*ve).saturating_mul(float_rao_emission)) + .map(|ve: &I32F32| { + I96F32::from_num(*ve) + .saturating_mul(float_rao_emission) + .saturating_mul(validator_cut) + }) .collect(); let validator_emission: Vec = validator_emission .iter() .map(|e: &I96F32| e.to_num::()) .collect(); - // Used only to track combined emission in the storage. let combined_emission: Vec = normalized_combined_emission .iter() diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d93253cfa..acbf4137c 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -434,6 +434,25 @@ pub mod pallet { pub fn DefaultSubnetOwnerCut() -> u16 { T::InitialSubnetOwnerCut::get() } + + #[pallet::type_value] + /// Default value for subnet miner cut. + pub fn DefaultSubnetMinerCut() -> u16 { + T::InitialSubnetMinerCut::get() + } + + #[pallet::type_value] + /// Default value for subnet validator cut. + pub fn DefaultSubnetValidatorCut() -> u16 { + T::InitialSubnetValidatorCut::get() + } + + #[pallet::type_value] + /// Default value for subnet burn cut. + pub fn DefaultSubnetBurnCut() -> u16 { + T::InitialSubnetBurnCut::get() + } + #[pallet::type_value] /// Default value for subnet limit. pub fn DefaultSubnetLimit() -> u16 { @@ -881,9 +900,27 @@ pub mod pallet { /// ITEM( network_lock_reduction_interval ) pub type NetworkLockReductionInterval = StorageValue<_, u64, ValueQuery, DefaultNetworkLockReductionInterval>; + #[pallet::storage] - /// ITEM( subnet_owner_cut ) - pub type SubnetOwnerCut = StorageValue<_, u16, ValueQuery, DefaultSubnetOwnerCut>; + /// MAP ( netuid ) --> subnet_owner_cut + pub type SubnetOwnerCut = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultSubnetOwnerCut>; + + #[pallet::storage] + /// MAP ( netuid ) --> subnet_miner_cut + pub type SubnetMinerCut = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultSubnetMinerCut>; + + #[pallet::storage] + /// MAP ( netuid ) --> subnet_validator_cut + pub type SubnetValidatorCut = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultSubnetValidatorCut>; + + #[pallet::storage] + /// MAP ( netuid ) --> subnet_burn_cut + pub type SubnetBurnCut = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultSubnetBurnCut>; + #[pallet::storage] /// ITEM( network_rate_limit ) pub type NetworkRateLimit = StorageValue<_, u64, ValueQuery, DefaultNetworkRateLimit>; diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 414af7e6b..7651f214d 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -174,6 +174,15 @@ mod config { /// Initial network subnet cut. #[pallet::constant] type InitialSubnetOwnerCut: Get; + /// Initial subnet miner cut. + #[pallet::constant] + type InitialSubnetMinerCut: Get; + /// Initial subnet validator cut. + #[pallet::constant] + type InitialSubnetValidatorCut: Get; + /// Initial subnet burn cut. + #[pallet::constant] + type InitialSubnetBurnCut: Get; /// Initial lock reduction interval. #[pallet::constant] type InitialNetworkLockReductionInterval: Get; diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index aab849994..563a6de85 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -190,5 +190,7 @@ mod errors { InputLengthsUnequal, /// A transactor exceeded the rate limit for setting weights. CommittingWeightsTooFast, + /// Invalid cut normalization. + InvalidCut, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index f3b03684d..3440e94ce 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -110,7 +110,13 @@ mod events { /// the faucet it called on the test net. Faucet(T::AccountId, u64), /// the subnet owner cut is set. - SubnetOwnerCutSet(u16), + SubnetOwnerCutSet(u16, u16), + /// the subnet miner cut is set. + SubnetMinerCutSet(u16, u16), + /// the subnet validator cut is set. + SubnetValidatorCutSet(u16, u16), + /// the subnet burn cut is set. + SubnetBurnCutSet(u16, u16), /// the network creation rate limit is set. NetworkRateLimitSet(u64), /// the network immunity period is set. diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 57cc38786..20974d6d9 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -22,6 +22,33 @@ impl Pallet { } } + pub fn calculate_burn_cut(total_cut: u32) -> Result { + let total_max = u32::from(u16::MAX); + if total_cut < total_max { + let burn_cut = total_max + .checked_sub(total_cut) + .ok_or(Error::::InvalidCut)?; + u16::try_from(burn_cut).map_err(|_| Error::::InvalidCut.into()) + } else { + Err(Error::::InvalidCut.into()) + } + } + + pub fn ensure_subnet_miner_cut(netuid: u16, cut: u16) -> Result { + let validator_cut = SubnetValidatorCut::::get(netuid); + let owner_cut = SubnetOwnerCut::::get(netuid); + let total_cut = (cut as u32) + .saturating_add(validator_cut as u32) + .saturating_add(owner_cut as u32); + Self::calculate_burn_cut(total_cut) + } + + pub fn ensure_subnet_validator_cut(netuid: u16, cut: u16) -> Result { + let validator_cut = SubnetValidatorCut::::get(netuid); + let total_cut = (cut as u32).saturating_add(validator_cut as u32); + Self::calculate_burn_cut(total_cut) + } + // ======================== // ==== Global Setters ==== // ======================== @@ -603,12 +630,39 @@ impl Pallet { pub fn get_subnet_owner(netuid: u16) -> T::AccountId { SubnetOwner::::get(netuid) } - pub fn get_subnet_owner_cut() -> u16 { - SubnetOwnerCut::::get() + pub fn get_subnet_owner_cut(netuid: u16) -> u16 { + SubnetOwnerCut::::get(netuid) + } + pub fn set_subnet_owner_cut(netuid: u16, subnet_owner_cut: u16) { + SubnetOwnerCut::::insert(netuid, subnet_owner_cut); + Self::deposit_event(Event::SubnetOwnerCutSet(netuid, subnet_owner_cut)); + } + + pub fn set_subnet_miner_cut(netuid: u16, subnet_miner_cut: u16) { + SubnetMinerCut::::insert(netuid, subnet_miner_cut); + Self::deposit_event(Event::SubnetMinerCutSet(netuid, subnet_miner_cut)); } - pub fn set_subnet_owner_cut(subnet_owner_cut: u16) { - SubnetOwnerCut::::set(subnet_owner_cut); - Self::deposit_event(Event::SubnetOwnerCutSet(subnet_owner_cut)); + + pub fn get_subnet_miner_cut(netuid: u16) -> u16 { + SubnetMinerCut::::get(netuid) + } + + pub fn set_subnet_validator_cut(netuid: u16, subnet_validator_cut: u16) { + SubnetValidatorCut::::insert(netuid, subnet_validator_cut); + Self::deposit_event(Event::SubnetValidatorCutSet(netuid, subnet_validator_cut)); + } + + pub fn get_subnet_validator_cut(netuid: u16) -> u16 { + SubnetValidatorCut::::get(netuid) + } + + pub fn set_subnet_burn_cut(netuid: u16, subnet_burn_cut: u16) { + SubnetBurnCut::::insert(netuid, subnet_burn_cut); + Self::deposit_event(Event::SubnetBurnCutSet(netuid, subnet_burn_cut)); + } + + pub fn get_subnet_burn_cut(netuid: u16) -> u16 { + SubnetBurnCut::::get(netuid) } pub fn get_owned_hotkeys(coldkey: &T::AccountId) -> Vec { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 7d33bff03..c110b726d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -952,6 +952,9 @@ parameter_types! { pub const SubtensorInitialMinAllowedUids: u16 = 128; pub const SubtensorInitialMinLockCost: u64 = 1_000_000_000_000; // 1000 TAO pub const SubtensorInitialSubnetOwnerCut: u16 = 11_796; // 18 percent + pub const SubtensorInitialSubnetMinerCut: u16 = 11_796; // 18 percent + pub const SubtensorInitialSubnetValidatorCut: u16 = 11_796; // 18 percent + pub const SubtensorInitialSubnetBurnCut: u16 = 30_146; // Remaining 46 percent pub const SubtensorInitialSubnetLimit: u16 = 12; pub const SubtensorInitialNetworkLockReductionInterval: u64 = 14 * 7200; pub const SubtensorInitialNetworkRateLimit: u64 = 7200; @@ -1018,6 +1021,9 @@ impl pallet_subtensor::Config for Runtime { type InitialNetworkMinLockCost = SubtensorInitialMinLockCost; type InitialNetworkLockReductionInterval = SubtensorInitialNetworkLockReductionInterval; type InitialSubnetOwnerCut = SubtensorInitialSubnetOwnerCut; + type InitialSubnetMinerCut = SubtensorInitialSubnetMinerCut; + type InitialSubnetValidatorCut = SubtensorInitialSubnetValidatorCut; + type InitialSubnetBurnCut = SubtensorInitialSubnetBurnCut; type InitialSubnetLimit = SubtensorInitialSubnetLimit; type InitialNetworkRateLimit = SubtensorInitialNetworkRateLimit; type InitialTargetStakesPerInterval = SubtensorInitialTargetStakesPerInterval;