From a4a78c47336e45e4aaf5887f311c75a4946a1d1c Mon Sep 17 00:00:00 2001 From: Honza Date: Thu, 14 Nov 2024 19:49:11 +0100 Subject: [PATCH] feat: new fee structure --- pallets/governance/src/lib.rs | 6 +- pallets/offworker/src/dispatches/mod.rs | 10 +- pallets/offworker/src/lib.rs | 2 +- pallets/offworker/src/util.rs | 2 +- .../src/distribute_emission.rs | 10 +- pallets/subnet_emission/src/lib.rs | 1 + pallets/subnet_emission/src/set_weights.rs | 8 +- .../src/subnet_consensus/util/consensus.rs | 2 +- pallets/subspace/src/lib.rs | 116 ++++++++++++++---- pallets/subspace/src/network/module.rs | 27 +--- pallets/subspace/src/network/registration.rs | 4 +- pallets/subspace/src/network/staking.rs | 14 ++- pallets/subspace/src/network/subnet.rs | 4 +- pallets/subspace/src/params/global.rs | 40 ++++-- pallets/subspace/src/params/module.rs | 81 +++++++----- pallets/subspace/src/selections/dispatches.rs | 13 +- pallets/subspace/src/selections/errors.rs | 8 ++ pallets/subspace/src/selections/genesis.rs | 10 +- runtime/src/lib.rs | 2 +- tests/src/governance.rs | 12 +- tests/src/mock.rs | 4 +- tests/src/offworker/offworker.rs | 2 + tests/src/subspace/registration.rs | 37 +++++- 23 files changed, 286 insertions(+), 129 deletions(-) diff --git a/pallets/governance/src/lib.rs b/pallets/governance/src/lib.rs index 1e96f3615..72653bb12 100644 --- a/pallets/governance/src/lib.rs +++ b/pallets/governance/src/lib.rs @@ -194,7 +194,8 @@ pub mod pallet { max_allowed_modules: u16, max_registrations_per_block: u16, max_allowed_weights: u16, - floor_delegation_fee: Percent, + floor_stake_delegation_fee: Percent, + floor_validator_weight_fee: Percent, floor_founder_share: u8, min_weight_stake: u64, curator: T::AccountId, @@ -212,7 +213,8 @@ pub mod pallet { params.max_allowed_modules = max_allowed_modules; params.max_registrations_per_block = max_registrations_per_block; params.max_allowed_weights = max_allowed_weights; - params.floor_delegation_fee = floor_delegation_fee; + params.floor_stake_delegation_fee = floor_stake_delegation_fee; + params.floor_validator_weight_fee = floor_validator_weight_fee; params.floor_founder_share = floor_founder_share; params.min_weight_stake = min_weight_stake; params.curator = curator; diff --git a/pallets/offworker/src/dispatches/mod.rs b/pallets/offworker/src/dispatches/mod.rs index 201f09c54..c65fff5a4 100644 --- a/pallets/offworker/src/dispatches/mod.rs +++ b/pallets/offworker/src/dispatches/mod.rs @@ -56,10 +56,12 @@ pub mod dispatches { let epoch_count = ConsensusParameters::::iter_prefix(subnet_id).count(); // Check if the length matches - ensure!( - decrypted_weights.len() == epoch_count, - Error::::DecryptedWeightsLengthMismatch - ); + + // TODO: get this back to life later + // ensure!( + // decrypted_weights.len() == epoch_count, + // Error::::DecryptedWeightsLengthMismatch + // ); let has_weights = decrypted_weights.iter().any(|(_, inner_vec)| { inner_vec.iter().any(|(_, weight_vec, _)| !weight_vec.is_empty()) diff --git a/pallets/offworker/src/lib.rs b/pallets/offworker/src/lib.rs index 5baf79f0f..202798858 100644 --- a/pallets/offworker/src/lib.rs +++ b/pallets/offworker/src/lib.rs @@ -31,7 +31,7 @@ use sp_std::collections::btree_map::BTreeMap; use pallet_subnet_emission::{ConsensusParameters, Weights}; use pallet_subspace::{ math::{inplace_normalize_64, vec_fixed64_to_fixed32}, - Consensus, CopierMargin, FloorDelegationFee, MaxEncryptionPeriod, + Consensus, CopierMargin, MaxEncryptionPeriod, MinFees, }; use parity_scale_codec::{Decode, Encode}; use scale_info::prelude::marker::PhantomData; diff --git a/pallets/offworker/src/util.rs b/pallets/offworker/src/util.rs index 2fa4b3a61..041e90d35 100644 --- a/pallets/offworker/src/util.rs +++ b/pallets/offworker/src/util.rs @@ -177,7 +177,7 @@ pub fn should_decrypt_weights( }; // Get delegation fee (this is not a Result type) - let delegation_fee = FloorDelegationFee::::get(); + let delegation_fee = MinFees::::get().stake_delegation_fee; // Update simulation result simulation_result.update(simulation_yuma_output, copier_uid, delegation_fee); diff --git a/pallets/subnet_emission/src/distribute_emission.rs b/pallets/subnet_emission/src/distribute_emission.rs index 260aae25c..45e40d78f 100644 --- a/pallets/subnet_emission/src/distribute_emission.rs +++ b/pallets/subnet_emission/src/distribute_emission.rs @@ -159,12 +159,12 @@ fn run_yuma_consensus(netuid: u16, emission_to_drain: u64) -> Result< } // This means, that subnet uses weight encryption. Create the parameters, only if subnet has // some encrypted weights present - // let encrypted_weights = WeightEncryptionData::::iter_prefix(netuid).collect::>(); + let encrypted_weights = WeightEncryptionData::::iter_prefix(netuid).collect::>(); - // if encrypted_weights.is_empty() { - // log::warn!("No encrypted weights found for subnet {netuid}"); - // return Ok(()); - // } + if encrypted_weights.is_empty() { + log::warn!("No encrypted weights found for subnet {netuid}"); + return Ok(()); + } // If subnet has some weights, create the parameters let mut params = ConsensusParams::::new(netuid, emission_to_drain)?; diff --git a/pallets/subnet_emission/src/lib.rs b/pallets/subnet_emission/src/lib.rs index c92aa2439..381cb7758 100644 --- a/pallets/subnet_emission/src/lib.rs +++ b/pallets/subnet_emission/src/lib.rs @@ -394,6 +394,7 @@ pub mod pallet { netuid, encrypted_weights, decrypted_weights_hash, + true, ) } diff --git a/pallets/subnet_emission/src/set_weights.rs b/pallets/subnet_emission/src/set_weights.rs index 16e478f94..eeed1eeb1 100644 --- a/pallets/subnet_emission/src/set_weights.rs +++ b/pallets/subnet_emission/src/set_weights.rs @@ -299,7 +299,8 @@ impl Pallet { fee_percentage: pallet_subspace::Pallet::::module_params( netuid, &target, target_uid, ) - .delegation_fee, + .fees + .validator_weight_fee, }), ); @@ -344,6 +345,7 @@ impl Pallet { netuid: u16, encrypted_weights: Vec, decrypted_weights_hash: Vec, + set_last_updated: bool, ) -> DispatchResult { let key = ensure_signed(origin.clone())?; @@ -377,7 +379,9 @@ impl Pallet { let current_block = pallet_subspace::Pallet::::get_current_block_number(); pallet_subspace::WeightSetAt::::insert(netuid, uid, current_block); - pallet_subspace::Pallet::::set_last_update_for_uid(netuid, uid, current_block); + if set_last_updated { + pallet_subspace::Pallet::::set_last_update_for_uid(netuid, uid, current_block); + } pallet_subspace::Pallet::::deposit_event(pallet_subspace::Event::WeightsSet( netuid, uid, )); diff --git a/pallets/subnet_emission/src/subnet_consensus/util/consensus.rs b/pallets/subnet_emission/src/subnet_consensus/util/consensus.rs index eea34cfd9..893b9ddb7 100644 --- a/pallets/subnet_emission/src/subnet_consensus/util/consensus.rs +++ b/pallets/subnet_emission/src/subnet_consensus/util/consensus.rs @@ -87,7 +87,7 @@ pub fn calculate_final_emissions( if validator_emission > 0 { let ownership_vector = PalletSubspace::::get_ownership_ratios(subnet_id, &module_key.0); - let delegation_fee = PalletSubspace::::get_delegation_fee(&module_key.0); + let delegation_fee = PalletSubspace::::get_stake_delegation_fee(&module_key.0); let total_validator_emission = I64F64::from_num(validator_emission); for (delegate_key, delegate_ratio) in ownership_vector { diff --git a/pallets/subspace/src/lib.rs b/pallets/subspace/src/lib.rs index 5e873dd63..fa726b21f 100644 --- a/pallets/subspace/src/lib.rs +++ b/pallets/subspace/src/lib.rs @@ -183,7 +183,8 @@ pub mod pallet { }, // Put here every module-related double map, that has no uid association. first key is netuid, second key is key of module (not uid!) key_only_storages: { - Metadata: Vec + Metadata: Vec, + WeightSettingDelegation: DelegationInfo } ); @@ -384,16 +385,6 @@ pub mod pallet { #[pallet::storage] pub type MaxAllowedModules = StorageValue<_, u16, ValueQuery, ConstU16<10_000>>; - #[pallet::type_value] - pub fn DefaultFloorDelegationFee() -> Percent { - Percent::from_percent(5) - } - - /// Minimum delegation fee - #[pallet::storage] - pub type FloorDelegationFee = - StorageValue<_, Percent, ValueQuery, DefaultFloorDelegationFee>; - /// Minimum stake weight #[pallet::storage] pub type MinWeightStake = StorageValue<_, u64, ValueQuery>; @@ -476,24 +467,105 @@ pub mod pallet { #[pallet::storage] pub type SubnetImmunityPeriod = StorageValue<_, u64, ValueQuery, ConstU64<32400>>; - #[pallet::type_value] - pub fn DefaultDelegationFee() -> Percent { - Percent::from_percent(5u8) - } - - /// Delegation fee per account - #[pallet::storage] - pub type DelegationFee = - StorageMap<_, Blake2_128Concat, T::AccountId, Percent, ValueQuery, DefaultDelegationFee>; - /// Control delegation per account #[pallet::storage] pub type WeightSettingDelegation = StorageDoubleMap<_, Identity, u16, Identity, T::AccountId, DelegationInfo>; - #[derive(Encode, Decode, Clone, PartialEq, TypeInfo)] + #[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug)] pub struct DelegationInfo { pub delegate: AccountId, pub fee_percentage: Percent, } + + // --- Module Fees --- + + /// Default values for fees used throughout the module + pub struct FeeDefaults; + impl FeeDefaults { + pub const STAKE_DELEGATION: Percent = Percent::from_percent(5); + pub const VALIDATOR_WEIGHT: Percent = Percent::from_percent(4); + } + + /// Contains the minimum allowed values for delegation fees + #[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Eq)] + pub struct MinimumFees { + /// Minimum fee for stake delegation + pub stake_delegation_fee: Percent, + /// Minimum fee for validator weight delegation + pub validator_weight_fee: Percent, + } + + #[pallet::type_value] + pub fn DefaultMinimumFees() -> MinimumFees { + MinimumFees { + stake_delegation_fee: FeeDefaults::STAKE_DELEGATION, + validator_weight_fee: FeeDefaults::VALIDATOR_WEIGHT, + } + } + + /// Storage for minimum fees that can be updated via runtime + #[pallet::storage] + pub type MinFees = StorageValue<_, MinimumFees, ValueQuery, DefaultMinimumFees>; + + /// A fee structure containing delegation fees for both stake and validator weight + #[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Eq)] + pub struct ValidatorFees { + /// Fee charged when delegators delegate their stake + pub stake_delegation_fee: Percent, + /// Fee charged when validators delegate their weight-setting authority + pub validator_weight_fee: Percent, + } + + impl Default for ValidatorFees { + fn default() -> Self { + Self { + stake_delegation_fee: FeeDefaults::STAKE_DELEGATION, + validator_weight_fee: FeeDefaults::VALIDATOR_WEIGHT, + } + } + } + + #[pallet::type_value] + pub fn DefaultValidatorFees() -> ValidatorFees { + ValidatorFees::default() + } + + /// Maps validator accounts to their fee configuration + #[pallet::storage] + pub type ValidatorFeeConfig = + StorageMap<_, Identity, T::AccountId, ValidatorFees, ValueQuery, DefaultValidatorFees>; + + impl ValidatorFees { + /// Creates a new ValidatorFees instance with validation against minimum fees + pub fn new( + stake_delegation_fee: Percent, + validator_weight_fee: Percent, + ) -> Result { + let min_fees = MinFees::::get(); + if stake_delegation_fee < min_fees.stake_delegation_fee { + return Err("Stake delegation fee is below minimum threshold"); + } + if validator_weight_fee < min_fees.validator_weight_fee { + return Err("Validator weight fee is below minimum threshold"); + } + + Ok(Self { + stake_delegation_fee, + validator_weight_fee, + }) + } + + /// Validates that the fees meet minimum requirements + pub fn validate(&self) -> Result<(), &'static str> { + let min_fees = MinFees::::get(); + if self.stake_delegation_fee < min_fees.stake_delegation_fee { + return Err("Stake delegation fee is below minimum threshold"); + } + if self.validator_weight_fee < min_fees.validator_weight_fee { + return Err("Validator weight fee is below minimum threshold"); + } + Ok(()) + } + } } diff --git a/pallets/subspace/src/network/module.rs b/pallets/subspace/src/network/module.rs index 54506719e..492525d82 100644 --- a/pallets/subspace/src/network/module.rs +++ b/pallets/subspace/src/network/module.rs @@ -8,18 +8,18 @@ impl Pallet { pub fn do_update_module( origin: T::RuntimeOrigin, netuid: u16, - changeset: ModuleChangeset, + changeset: ModuleChangeset, ) -> DispatchResult { let key = ensure_signed(origin)?; let uid: u16 = Self::get_uid_for_key(netuid, &key).ok_or(Error::::ModuleDoesNotExist)?; - changeset.apply::(netuid, key, uid)?; + changeset.apply(netuid, key, uid)?; Ok(()) } pub fn append_module( netuid: u16, key: &T::AccountId, - changeset: ModuleChangeset, + changeset: ModuleChangeset, ) -> Result { // --- Get The Next Uid --- let uid: u16 = N::::get(netuid); @@ -28,7 +28,7 @@ impl Pallet { // -- Initialize All Storages --- StorageHandler::initialize_all::(netuid, uid, key)?; // Make sure this overwrites the defaults (keep it second) - changeset.apply::(netuid, key.clone(), uid)?; + changeset.apply(netuid, key.clone(), uid)?; // --- Update The Network Module Size --- N::::mutate(netuid, |n| *n = n.saturating_add(1)); @@ -80,7 +80,7 @@ impl Pallet { // So the values are not "just hanging around" in the storage. Without module actually being // registered on any subnet. if Uids::::iter().all(|(_, key, _)| key != module_key) { - DelegationFee::::remove(&module_key); + ValidatorFeeConfig::::remove(&module_key); Self::remove_stake_from_storage(&module_key); } @@ -90,11 +90,6 @@ impl Pallet { *v }); - // 10. Handle rootnet deregistration - if let Some(key) = Self::get_key_for_uid(uid, netuid) { - Self::handle_weight_setting_delegation(key, netuid); - } - // 11. Remove subnet if empty if deregister_subnet_if_empty && module_count == 0 { Self::remove_subnet(netuid); @@ -102,16 +97,4 @@ impl Pallet { Ok(()) } - - fn handle_weight_setting_delegation(key: T::AccountId, netuid: u16) { - WeightSettingDelegation::::remove(netuid, &key); - - WeightSettingDelegation::::translate(|nuid, _, v: DelegationInfo| { - if nuid == netuid && v.delegate == key { - None - } else { - Some(v) - } - }); - } } diff --git a/pallets/subspace/src/network/registration.rs b/pallets/subspace/src/network/registration.rs index fc0f3a458..6dd18fe37 100644 --- a/pallets/subspace/src/network/registration.rs +++ b/pallets/subspace/src/network/registration.rs @@ -198,8 +198,8 @@ impl Pallet { address: Vec, metadata: Option>, ) -> Result { - let fee = DefaultDelegationFee::::get(); - let module_changeset = ModuleChangeset::new(name, address, fee, metadata); + let fees = DefaultValidatorFees::::get(); + let module_changeset = ModuleChangeset::new(name, address, fees, metadata, None); Self::append_module(netuid, module_key, module_changeset) } diff --git a/pallets/subspace/src/network/staking.rs b/pallets/subspace/src/network/staking.rs index 72b8067bd..5bf949240 100644 --- a/pallets/subspace/src/network/staking.rs +++ b/pallets/subspace/src/network/staking.rs @@ -314,12 +314,14 @@ impl Pallet { .sum() } - // Returns the delegation fee of a module - pub fn get_delegation_fee(module_key: &T::AccountId) -> Percent { - let min_deleg_fee_global = FloorDelegationFee::::get(); - let delegation_fee = DelegationFee::::get(module_key); - - delegation_fee.max(min_deleg_fee_global) + /// Returns staking delegation fee of a module + pub fn get_stake_delegation_fee(module_key: &T::AccountId) -> Percent { + // Get the validator's fee configuration + let validator_fees = ValidatorFeeConfig::::get(module_key); + + // Return the stake delegation fee, which will already be at or above + // MIN_STAKE_DELEGATION_FEE due to the ValidatorFees validation + validator_fees.stake_delegation_fee } pub fn has_enough_stake(key: &T::AccountId, module_key: &T::AccountId, amount: u64) -> bool { diff --git a/pallets/subspace/src/network/subnet.rs b/pallets/subspace/src/network/subnet.rs index 6c38a1f0d..86a8ff656 100644 --- a/pallets/subspace/src/network/subnet.rs +++ b/pallets/subspace/src/network/subnet.rs @@ -240,11 +240,11 @@ impl Pallet { .map(|(_, account, _)| account) .collect(); - // Clear delegation fees for accounts that exist only in this subnet + // Clear validator fees for accounts that exist only in this subnet Uids::::iter_prefix(subnet_id) .map(|(account, _)| account) .filter(|account| !accounts_in_other_subnets.contains(account)) - .for_each(|subnet_only_account| DelegationFee::::remove(&subnet_only_account)); + .for_each(|subnet_only_account| ValidatorFeeConfig::::remove(&subnet_only_account)); } pub fn get_total_subnets() -> u16 { diff --git a/pallets/subspace/src/params/global.rs b/pallets/subspace/src/params/global.rs index 3f51ff451..cc65e07e7 100644 --- a/pallets/subspace/src/params/global.rs +++ b/pallets/subspace/src/params/global.rs @@ -18,9 +18,10 @@ pub struct GlobalParams { pub max_allowed_weights: u16, // max number of weights per module // mins - pub floor_delegation_fee: Percent, // min delegation fee - pub floor_founder_share: u8, // min founder share - pub min_weight_stake: u64, // min weight stake required + pub floor_stake_delegation_fee: Percent, // min delegation fee + pub floor_validator_weight_fee: Percent, // min weight-setting delegation fee + pub floor_founder_share: u8, // min founder share + pub min_weight_stake: u64, // min weight stake required // S0 governance pub curator: T::AccountId, @@ -44,7 +45,8 @@ impl Pallet { max_allowed_modules: MaxAllowedModules::::get(), curator: T::get_curator(), floor_founder_share: FloorFounderShare::::get(), - floor_delegation_fee: FloorDelegationFee::::get(), + floor_stake_delegation_fee: MinFees::::get().stake_delegation_fee, + floor_validator_weight_fee: MinFees::::get().validator_weight_fee, // registrations max_registrations_per_block: MaxRegistrationsPerBlock::::get(), // weights @@ -72,7 +74,8 @@ impl Pallet { max_allowed_modules, max_registrations_per_block, max_allowed_weights, - floor_delegation_fee, + floor_stake_delegation_fee, + floor_validator_weight_fee, floor_founder_share, min_weight_stake, curator, @@ -88,7 +91,12 @@ impl Pallet { MinNameLength::::put(min_name_length); MaxAllowedSubnets::::put(max_allowed_subnets); MaxAllowedModules::::put(max_allowed_modules); - FloorDelegationFee::::put(floor_delegation_fee); + + // Update minimum fees + MinFees::::put(MinimumFees { + stake_delegation_fee: floor_stake_delegation_fee, + validator_weight_fee: floor_validator_weight_fee, + }); // Registration and weight parameters MaxRegistrationsPerBlock::::set(max_registrations_per_block); @@ -120,7 +128,8 @@ impl Pallet { max_allowed_modules, max_registrations_per_block, max_allowed_weights, - floor_delegation_fee, + floor_stake_delegation_fee, + floor_validator_weight_fee, floor_founder_share, min_weight_stake: _, curator: _, @@ -140,14 +149,19 @@ impl Pallet { Error::::InvalidMinNameLenght ); - // Delegation fee validation + // Fee validations using ValidatorFees validation + ValidatorFees::new::(*floor_stake_delegation_fee, *floor_validator_weight_fee) + .map_err(|_| Error::::InvalidMinFees)?; + + // Additional validation to ensure fees don't decrease ensure!( - floor_delegation_fee.deconstruct() <= 100 - && floor_delegation_fee.deconstruct() - >= old_params.floor_delegation_fee.deconstruct(), - Error::::InvalidMinDelegationFee + floor_stake_delegation_fee >= &old_params.floor_stake_delegation_fee, + Error::::CannotDecreaseFee + ); + ensure!( + floor_validator_weight_fee >= &old_params.floor_validator_weight_fee, + Error::::CannotDecreaseFee ); - // Subnet and module validations ensure!( *max_allowed_subnets > 0, diff --git a/pallets/subspace/src/params/module.rs b/pallets/subspace/src/params/module.rs index 8bcc59df2..209025559 100644 --- a/pallets/subspace/src/params/module.rs +++ b/pallets/subspace/src/params/module.rs @@ -1,54 +1,57 @@ use crate::*; use scale_info::TypeInfo; -use sp_arithmetic::per_things::Percent; #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct ModuleParams { pub name: Vec, pub address: Vec, - pub delegation_fee: Percent, + pub fees: ValidatorFees, pub metadata: Option>, pub _pd: PhantomData, } #[derive(Debug)] -pub struct ModuleChangeset { +pub struct ModuleChangeset { pub name: Option>, pub address: Option>, - pub delegation_fee: Option, + pub fees: Option, pub metadata: Option>, + pub weight_setting_delegation: Option>, } -impl ModuleChangeset { +impl ModuleChangeset { #[must_use] pub fn new( name: Vec, address: Vec, - delegation_fee: Percent, + fees: ValidatorFees, metadata: Option>, + weight_setting_delegation: Option>, ) -> Self { Self { name: Some(name), address: Some(address), - delegation_fee: Some(delegation_fee), + fees: Some(fees), metadata, + weight_setting_delegation, } } #[deny(unused_variables)] #[must_use] - pub fn update( + pub fn update( params: &ModuleParams, name: Vec, address: Vec, - delegation_fee: Option, + fees: Option, metadata: Option>, + weight_setting_delegation: Option>, ) -> Self { let ModuleParams { name: old_name, address: old_address, - delegation_fee: _, + fees: _, metadata: _, _pd: _, } = params; @@ -56,18 +59,20 @@ impl ModuleChangeset { Self { name: (name != *old_name).then_some(name), address: (address != *old_address).then_some(address), - delegation_fee, + fees, metadata, + weight_setting_delegation, } } #[deny(unused_variables)] - pub fn validate(&self, netuid: u16) -> Result<(), sp_runtime::DispatchError> { + pub fn validate(&self, netuid: u16) -> Result<(), sp_runtime::DispatchError> { let Self { name, address, - delegation_fee, + fees, metadata, + weight_setting_delegation, } = self; let max_length = MaxNameLength::::get() as usize; @@ -81,31 +86,36 @@ impl ModuleChangeset { ModuleValidator::validate_address::(address, max_length)?; } - if let Some(fee) = delegation_fee { - ModuleValidator::validate_delegation_fee::(fee)?; + if let Some(fees) = fees { + ModuleValidator::validate_fees::(fees)?; } if let Some(metadata) = metadata { ModuleValidator::validate_metadata::(metadata)?; } + if let Some(delegation_info) = weight_setting_delegation { + ModuleValidator::validate_weight_setting_delegation::(delegation_info)?; + } + Ok(()) } #[deny(unused_variables)] - pub fn apply( + pub fn apply( self, netuid: u16, key: T::AccountId, uid: u16, ) -> Result<(), sp_runtime::DispatchError> { - self.validate::(netuid)?; + self.validate(netuid)?; let Self { name, address, - delegation_fee, + fees, metadata, + weight_setting_delegation, } = self; if let Some(new_name) = name { @@ -116,14 +126,18 @@ impl ModuleChangeset { Address::::insert(netuid, uid, new_address); } - if let Some(new_fee) = delegation_fee { - DelegationFee::::insert(&key, new_fee); + if let Some(new_fees) = fees { + ValidatorFeeConfig::::insert(&key, new_fees); } if let Some(new_metadata) = metadata { Metadata::::insert(netuid, &key, new_metadata); } + if let Some(delegation_info) = weight_setting_delegation { + WeightSettingDelegation::::insert(netuid, &key, delegation_info); + } + Pallet::::deposit_event(Event::ModuleUpdated(netuid, key)); Ok(()) } @@ -162,22 +176,27 @@ impl ModuleValidator { Ok(()) } - pub fn validate_delegation_fee( - fee: &Percent, - ) -> Result<(), sp_runtime::DispatchError> { - ensure!( - *fee >= FloorDelegationFee::::get(), - Error::::InvalidMinDelegationFee - ); - Ok(()) - } - pub fn validate_metadata(metadata: &[u8]) -> Result<(), sp_runtime::DispatchError> { ensure!(!metadata.is_empty(), Error::::InvalidModuleMetadata); ensure!(metadata.len() <= 120, Error::::ModuleMetadataTooLong); core::str::from_utf8(metadata).map_err(|_| Error::::InvalidModuleMetadata)?; Ok(()) } + + pub fn validate_fees(fees: &ValidatorFees) -> Result<(), sp_runtime::DispatchError> { + fees.validate::().map_err(|_| Error::::InvalidMinDelegationFee)?; + Ok(()) + } + + pub fn validate_weight_setting_delegation( + delegation_info: &DelegationInfo, + ) -> Result<(), sp_runtime::DispatchError> { + ensure!( + delegation_info.fee_percentage >= MinFees::::get().validator_weight_fee, + Error::::InvalidMinDelegationFee + ); + Ok(()) + } } impl Pallet { @@ -185,7 +204,7 @@ impl Pallet { ModuleParams { name: Name::::get(netuid, uid), address: Address::::get(netuid, uid), - delegation_fee: DelegationFee::::get(key), + fees: ValidatorFeeConfig::::get(key), metadata: Metadata::::get(netuid, key), _pd: PhantomData, } diff --git a/pallets/subspace/src/selections/dispatches.rs b/pallets/subspace/src/selections/dispatches.rs index b2a0e98c3..6c85d65f9 100644 --- a/pallets/subspace/src/selections/dispatches.rs +++ b/pallets/subspace/src/selections/dispatches.rs @@ -91,8 +91,9 @@ pub mod dispatches { netuid: u16, name: Vec, address: Vec, - delegation_fee: Option, + fees: Option, metadata: Option>, + weight_setting_delegation: Option>, ) -> DispatchResult { let key = ensure_signed(origin.clone())?; ensure!( @@ -104,8 +105,14 @@ pub mod dispatches { let params = Self::module_params(netuid, &key, uid); - let changeset = - ModuleChangeset::update(¶ms, name, address, delegation_fee, metadata); + let changeset = ModuleChangeset::update( + ¶ms, + name, + address, + fees, + metadata, + weight_setting_delegation, + ); Self::do_update_module(origin, netuid, changeset) } diff --git a/pallets/subspace/src/selections/errors.rs b/pallets/subspace/src/selections/errors.rs index 298f1aa4c..85cc2ea49 100644 --- a/pallets/subspace/src/selections/errors.rs +++ b/pallets/subspace/src/selections/errors.rs @@ -201,5 +201,13 @@ pub mod errors { InvalidMaximumSetWeightCallsPerEpoch, /// Some module parameter is invalid InvalidModuleParams, + /// The provided minimum fees are invalid. This can happen when: + /// - Stake delegation fee is below the system minimum + /// - Validator weight fee is below the system minimum + /// - Either fee exceeds 100% + InvalidMinFees, + /// Cannot decrease fees below their current values. + /// Fees can only be increased to prevent economic attacks. + CannotDecreaseFee, } } diff --git a/pallets/subspace/src/selections/genesis.rs b/pallets/subspace/src/selections/genesis.rs index 34927493f..3f302039f 100644 --- a/pallets/subspace/src/selections/genesis.rs +++ b/pallets/subspace/src/selections/genesis.rs @@ -46,7 +46,7 @@ pub mod genesis { log::info!("registering subnet {netuid} with params: {params:?}"); - let fee = DelegationFee::::get(¶ms.founder); + let fee = crate::Pallet::::get_stake_delegation_fee(¶ms.founder); let changeset: SubnetChangeset = SubnetChangeset::new(params).expect("genesis subnets are valid"); let _ = self::Pallet::::add_subnet(changeset, Some(netuid)) @@ -55,10 +55,16 @@ pub mod genesis { for (module_uid, module) in subnet.modules.iter().enumerate() { let module_uid = module_uid as u16; + let fees = ValidatorFees { + stake_delegation_fee: fee, + validator_weight_fee: DefaultValidatorFees::::get().validator_weight_fee, + }; + let changeset = ModuleChangeset::new( module.name.clone(), module.address.clone(), - fee, + fees, + None, None, ); self::Pallet::::append_module(netuid, &module.key, changeset) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 6a1690307..b340802c4 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -998,7 +998,7 @@ impl_runtime_apis! { params: ModuleParams { name: params.name, address: params.address, - delegation_fee: params.delegation_fee, + delegation_fee: params.fees.stake_delegation_fee, metadata: params.metadata, } } diff --git a/tests/src/governance.rs b/tests/src/governance.rs index 6e75487e9..76a8b9479 100644 --- a/tests/src/governance.rs +++ b/tests/src/governance.rs @@ -72,7 +72,8 @@ fn global_proposal_validates_parameters() { max_allowed_modules, max_registrations_per_block, max_allowed_weights, - floor_delegation_fee, + floor_stake_delegation_fee, + floor_validator_weight_fee, floor_founder_share, min_weight_stake, curator, @@ -92,7 +93,8 @@ fn global_proposal_validates_parameters() { max_allowed_modules, max_registrations_per_block, max_allowed_weights, - floor_delegation_fee, + floor_stake_delegation_fee, + floor_validator_weight_fee, floor_founder_share, min_weight_stake, curator, @@ -247,7 +249,8 @@ fn global_params_proposal_accepted() { max_allowed_modules, max_registrations_per_block, max_allowed_weights, - floor_delegation_fee, + floor_stake_delegation_fee, + floor_validator_weight_fee, floor_founder_share, min_weight_stake, curator, @@ -269,7 +272,8 @@ fn global_params_proposal_accepted() { max_allowed_modules, max_registrations_per_block, max_allowed_weights, - floor_delegation_fee, + floor_stake_delegation_fee, + floor_validator_weight_fee, floor_founder_share, min_weight_stake, curator, diff --git a/tests/src/mock.rs b/tests/src/mock.rs index dd0f9de40..b1011550c 100644 --- a/tests/src/mock.rs +++ b/tests/src/mock.rs @@ -645,12 +645,14 @@ pub fn set_weights_encrypted( key: AccountId, encrypted_weights: Vec, decrypted_weights_hash: Vec, + set_last_updated: bool, ) { - SubnetEmissionMod::set_weights_encrypted( + SubnetEmissionMod::do_set_weights_encrypted( get_origin(key), netuid, encrypted_weights, decrypted_weights_hash, + set_last_updated, ) .unwrap(); } diff --git a/tests/src/offworker/offworker.rs b/tests/src/offworker/offworker.rs index 0c87dbbf5..8d636ea8c 100644 --- a/tests/src/offworker/offworker.rs +++ b/tests/src/offworker/offworker.rs @@ -155,6 +155,8 @@ fn test_offchain_worker_behavior() { key, encrypted_weights, decrypted_weights_hash, + false, /* The last update is manually overwriten based on the + * dataset */ ); } input_decrypted_weights.push((uid, weight_vec)); diff --git a/tests/src/subspace/registration.rs b/tests/src/subspace/registration.rs index ec98abe89..a372ba953 100644 --- a/tests/src/subspace/registration.rs +++ b/tests/src/subspace/registration.rs @@ -325,6 +325,7 @@ mod module_validation { addr.to_vec(), None, None, + None, ) }); @@ -332,13 +333,19 @@ mod module_validation { let origin_1 = get_origin(key_1); assert_ok!(register_custom(0, key_1, b"test2", b"0.0.0.0:2")); + let fees = pallet_subspace::ValidatorFees { + stake_delegation_fee: Percent::from_percent(5), + validator_weight_fee: Percent::from_percent(5), + }; + let update_module = |name: &[u8], addr: &[u8]| { SubspaceMod::update_module( origin_1.clone(), subnet, name.to_vec(), addr.to_vec(), - Some(Percent::from_percent(5)), + Some(fees.clone()), + None, None, ) }; @@ -355,15 +362,37 @@ mod module_validation { assert_eq!(params.name, b"test3"); assert_eq!(params.address, b"0.0.0.0:3"); - FloorDelegationFee::::put(Percent::from_percent(10)); + // Set higher minimum fees + MinFees::::put(MinimumFees { + stake_delegation_fee: Percent::from_percent(10), + validator_weight_fee: Percent::from_percent(10), + }); + + // Should fail now because fees are too low assert_err!( update_module(b"test3", b"0.0.0.0:3"), - Error::::InvalidMinDelegationFee + Error::::InvalidMinFees ); + + // Update fees to valid values + let valid_fees = pallet_subspace::ValidatorFees { + stake_delegation_fee: Percent::from_percent(10), + validator_weight_fee: Percent::from_percent(10), + }; + + // Should work with valid fees + assert_ok!(SubspaceMod::update_module( + origin_1.clone(), + subnet, + b"test3".to_vec(), + b"0.0.0.0:3".to_vec(), + Some(valid_fees), + None, + None, + )); }); } } - mod subnet_validation { use super::*;