diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index aee2059f66..d7d708e7d2 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -102,6 +102,15 @@ pub mod pallet { Ok(()) } + #[pallet::call_index(45)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_tx_delegate_take_rate_limit(origin: OriginFor, tx_rate_limit: u64) -> DispatchResult { + ensure_root(origin)?; + T::Subtensor::set_tx_delegate_take_rate_limit(tx_rate_limit); + log::info!("TxRateLimitDelegateTakeSet( tx_delegate_take_rate_limit: {:?} ) ", tx_rate_limit); + Ok(()) + } + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::sudo_set_serving_rate_limit())] pub fn sudo_set_serving_rate_limit( @@ -809,6 +818,7 @@ impl AuraInterface for () { pub trait SubtensorInterface { fn set_default_take(default_take: u16); fn set_tx_rate_limit(rate_limit: u64); + fn set_tx_delegate_take_rate_limit(rate_limit: u64); fn set_serving_rate_limit(netuid: u16, rate_limit: u64); diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 441534489d..84d517f1b5 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -79,6 +79,7 @@ parameter_types! { pub const InitialWeightsVersionKey: u16 = 0; pub const InitialServingRateLimit: u64 = 0; // No limit. pub const InitialTxRateLimit: u64 = 0; // Disable rate limit for testing + pub const InitialTxDelegateTakeRateLimit: u64 = 0; // Disable rate limit for testing pub const InitialBurn: u64 = 0; pub const InitialMinBurn: u64 = 0; pub const InitialMaxBurn: u64 = 1_000_000_000; @@ -143,6 +144,7 @@ impl pallet_subtensor::Config for Test { type InitialMinDifficulty = InitialMinDifficulty; type InitialServingRateLimit = InitialServingRateLimit; type InitialTxRateLimit = InitialTxRateLimit; + type InitialTxDelegateTakeRateLimit = InitialTxDelegateTakeRateLimit; type InitialBurn = InitialBurn; type InitialMaxBurn = InitialMaxBurn; type InitialMinBurn = InitialMinBurn; @@ -211,6 +213,10 @@ impl pallet_admin_utils::SubtensorInterface f SubtensorModule::set_tx_rate_limit(rate_limit); } + fn set_tx_delegate_take_rate_limit(rate_limit: u64) { + SubtensorModule::set_tx_delegate_take_rate_limit(rate_limit); + } + fn set_serving_rate_limit(netuid: u16, rate_limit: u64) { SubtensorModule::set_serving_rate_limit(netuid, rate_limit); } diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index 0647988157..c30f94d0b7 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -9,7 +9,7 @@ mod mock; use mock::*; #[allow(dead_code)] -pub fn add_network(netuid: u16, tempo: u16) { +pub fn add_network(netuid: u16, tempo: u16, _modality: u16) { SubtensorModule::init_new_network(netuid, tempo); SubtensorModule::set_network_registration_allowed(netuid, true); SubtensorModule::set_network_pow_registration_allowed(netuid, true); @@ -65,7 +65,7 @@ fn test_sudo_set_min_difficulty() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u64 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u64 = SubtensorModule::get_min_difficulty(netuid); assert_eq!( AdminUtils::sudo_set_min_difficulty( @@ -98,7 +98,7 @@ fn test_sudo_set_max_difficulty() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u64 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u64 = SubtensorModule::get_max_difficulty(netuid); assert_eq!( AdminUtils::sudo_set_max_difficulty( @@ -131,7 +131,7 @@ fn test_sudo_set_weights_version_key() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u64 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u64 = SubtensorModule::get_weights_version_key(netuid); assert_eq!( AdminUtils::sudo_set_weights_version_key( @@ -164,7 +164,7 @@ fn test_sudo_set_weights_set_rate_limit() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u64 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u64 = SubtensorModule::get_weights_set_rate_limit(netuid); assert_eq!( AdminUtils::sudo_set_weights_set_rate_limit( @@ -203,7 +203,7 @@ fn test_sudo_set_adjustment_interval() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_adjustment_interval(netuid); assert_eq!( AdminUtils::sudo_set_adjustment_interval( @@ -236,7 +236,7 @@ fn test_sudo_set_adjustment_alpha() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u64 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u64 = SubtensorModule::get_adjustment_alpha(netuid); assert_eq!( AdminUtils::sudo_set_adjustment_alpha( @@ -290,7 +290,7 @@ fn test_sudo_set_max_weight_limit() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_max_weight_limit(netuid); assert_eq!( AdminUtils::sudo_set_max_weight_limit( @@ -342,7 +342,7 @@ fn test_sudo_set_immunity_period() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_immunity_period(netuid); assert_eq!( AdminUtils::sudo_set_immunity_period( @@ -375,7 +375,7 @@ fn test_sudo_set_min_allowed_weights() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_min_allowed_weights(netuid); assert_eq!( AdminUtils::sudo_set_min_allowed_weights( @@ -408,7 +408,7 @@ fn test_sudo_set_max_allowed_uids() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_max_allowed_uids(netuid); assert_eq!( AdminUtils::sudo_set_max_allowed_uids( @@ -441,7 +441,7 @@ fn test_sudo_set_and_decrease_max_allowed_uids() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_max_allowed_uids(netuid); assert_eq!( AdminUtils::sudo_set_max_allowed_uids( @@ -478,7 +478,7 @@ fn test_sudo_set_kappa() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_kappa(netuid); assert_eq!( AdminUtils::sudo_set_kappa( @@ -511,7 +511,7 @@ fn test_sudo_set_rho() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_rho(netuid); assert_eq!( AdminUtils::sudo_set_rho( @@ -544,7 +544,7 @@ fn test_sudo_set_activity_cutoff() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_activity_cutoff(netuid); assert_eq!( AdminUtils::sudo_set_activity_cutoff( @@ -577,7 +577,7 @@ fn test_sudo_set_target_registrations_per_interval() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_target_registrations_per_interval(netuid); assert_eq!( AdminUtils::sudo_set_target_registrations_per_interval( @@ -616,7 +616,7 @@ fn test_sudo_set_difficulty() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u64 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u64 = SubtensorModule::get_difficulty_as_u64(netuid); assert_eq!( AdminUtils::sudo_set_difficulty( @@ -649,7 +649,7 @@ fn test_sudo_set_max_allowed_validators() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_max_allowed_validators(netuid); assert_eq!( AdminUtils::sudo_set_max_allowed_validators( @@ -751,7 +751,7 @@ fn test_sudo_set_bonds_moving_average() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u64 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u64 = SubtensorModule::get_bonds_moving_average(netuid); assert_eq!( AdminUtils::sudo_set_bonds_moving_average( @@ -787,7 +787,7 @@ fn test_sudo_set_rao_recycled() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u64 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u64 = SubtensorModule::get_rao_recycled(netuid); // Need to run from genesis block @@ -852,7 +852,7 @@ fn test_sudo_set_subnet_limit() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u16 = 10; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u16 = SubtensorModule::get_max_subnets(); assert_eq!( @@ -876,7 +876,7 @@ fn test_sudo_set_network_lock_reduction_interval() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: u64 = 7200; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: u64 = SubtensorModule::get_lock_reduction_interval(); assert_eq!( @@ -900,7 +900,7 @@ fn test_sudo_set_network_pow_registration_allowed() { new_test_ext().execute_with(|| { let netuid: u16 = 1; let to_be_set: bool = true; - add_network(netuid, 10); + add_network(netuid, 10, 0); let init_value: bool = SubtensorModule::get_network_pow_registration_allowed(netuid); assert_eq!( @@ -926,3 +926,45 @@ fn test_sudo_set_network_pow_registration_allowed() { ); }); } + +#[test] +fn test_sudo_set_tx_rate_limit() { + new_test_ext().execute_with(|| { + let to_be_set: u64 = 10; + let init_value: u64 = SubtensorModule::get_tx_rate_limit(); + assert_eq!( + AdminUtils::sudo_set_tx_rate_limit( + <::RuntimeOrigin>::signed(U256::from(1)), + to_be_set + ), + Err(DispatchError::BadOrigin.into()) + ); + assert_eq!(SubtensorModule::get_tx_rate_limit(), init_value); + assert_ok!(AdminUtils::sudo_set_tx_rate_limit( + <::RuntimeOrigin>::root(), + to_be_set + )); + assert_eq!(SubtensorModule::get_tx_rate_limit(), to_be_set); + }); +} + +#[test] +fn test_sudo_set_tx_delegate_take_rate_limit() { + new_test_ext().execute_with(|| { + let to_be_set: u64 = 10; + let init_value: u64 = SubtensorModule::get_tx_delegate_take_rate_limit(); + assert_eq!( + AdminUtils::sudo_set_tx_delegate_take_rate_limit( + <::RuntimeOrigin>::signed(U256::from(1)), + to_be_set + ), + Err(DispatchError::BadOrigin.into()) + ); + assert_eq!(SubtensorModule::get_tx_delegate_take_rate_limit(), init_value); + assert_ok!(AdminUtils::sudo_set_tx_delegate_take_rate_limit( + <::RuntimeOrigin>::root(), + to_be_set + )); + assert_eq!(SubtensorModule::get_tx_delegate_take_rate_limit(), to_be_set); + }); +} diff --git a/pallets/registry/src/types.rs b/pallets/registry/src/types.rs index 3eeba58ac5..5db1371ae6 100644 --- a/pallets/registry/src/types.rs +++ b/pallets/registry/src/types.rs @@ -15,7 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// use super::*; use codec::{Decode, Encode, MaxEncodedLen}; use enumflags2::{bitflags, BitFlags}; use frame_support::{ diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 0490098a34..88355d96e4 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "512"] +#![allow(non_snake_case, non_camel_case_types)] // Edit this file to define custom logic or remove it if it is not needed. // Learn more about FRAME and the core library of Substrate FRAME pallets: // @@ -165,6 +166,8 @@ pub mod pallet { type InitialServingRateLimit: Get; #[pallet::constant] // Initial transaction rate limit. type InitialTxRateLimit: Get; + #[pallet::constant] // Initial delegate take transaction rate limit. + type InitialTxDelegateTakeRateLimit: Get; #[pallet::constant] // Initial percentage of total stake required to join senate. type InitialSenateRequiredStakePercentage: Get; #[pallet::constant] // Initial adjustment alpha on burn and pow. @@ -612,15 +615,24 @@ pub mod pallet { T::InitialTxRateLimit::get() } #[pallet::type_value] + pub fn DefaultTxDelegateTakeRateLimit() -> u64 { + T::InitialTxDelegateTakeRateLimit::get() + } + #[pallet::type_value] pub fn DefaultLastTxBlock() -> u64 { 0 } #[pallet::storage] // --- ITEM ( tx_rate_limit ) pub(super) type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; + #[pallet::storage] // --- ITEM ( tx_rate_limit ) + pub(super) type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; #[pallet::storage] // --- MAP ( key ) --> last_block pub(super) type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + #[pallet::storage] // --- MAP ( key ) --> last_block + pub(super) type LastTxBlockDelegateTake = + StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; #[pallet::type_value] pub fn DefaultServingRateLimit() -> u64 { @@ -929,6 +941,7 @@ pub mod pallet { MaxBurnSet(u16, u64), // --- Event created when setting max burn on a network. MinBurnSet(u16, u64), // --- Event created when setting min burn on a network. TxRateLimitSet(u64), // --- Event created when setting the transaction rate limit. + TxDelegateTakeRateLimitSet(u64), // --- Event created when setting the delegate take transaction rate limit. Sudid(DispatchResult), // --- Event created when a sudo call is done. RegistrationAllowed(u16, bool), // --- Event created when registration is allowed/disallowed for a subnet. PowRegistrationAllowed(u16, bool), // --- Event created when POW registration is allowed/disallowed for a subnet. @@ -944,6 +957,8 @@ pub mod pallet { NetworkMinLockCostSet(u64), // Event created when the network minimum locking cost is set. SubnetLimitSet(u16), // Event created when the maximum number of subnets is set NetworkLockCostReductionIntervalSet(u64), // Event created when the lock cost reduction is set + TakeDecreased( T::AccountId, T::AccountId, u16 ), // Event created when the take for a delegate is decreased. + TakeIncreased( T::AccountId, T::AccountId, u16 ), // Event created when the take for a delegate is increased. HotkeySwapped { coldkey: T::AccountId, old_hotkey: T::AccountId, @@ -1013,6 +1028,7 @@ pub mod pallet { StakeTooLowForRoot, // --- Thrown when a hotkey attempts to join the root subnet with too little stake AllNetworksInImmunity, // --- Thrown when all subnets are in the immunity period NotEnoughBalance, + InvalidTake, // --- Thrown when delegate take is being set out of bounds } // ================== @@ -1352,6 +1368,78 @@ pub mod pallet { Self::do_become_delegate(origin, hotkey, Self::get_default_take()) } + // --- Allows delegates to decrease its take value. + // + // # Args: + // * 'origin': (::Origin): + // - The signature of the caller's coldkey. + // + // * 'hotkey' (T::AccountId): + // - The hotkey we are delegating (must be owned by the coldkey.) + // + // * 'take' (u16): + // - The new stake proportion that this hotkey takes from delegations. + // The new value can be between 0 and 11_796 and should be strictly + // lower than the previous value. It T is the new value (rational number), + // the the parameter is calculated as [65535 * T]. For example, 1% would be + // [0.01 * 65535] = [655.35] = 655 + // + // # Event: + // * TakeDecreased; + // - On successfully setting a decreased take for this hotkey. + // + // # Raises: + // * 'NotRegistered': + // - The hotkey we are delegating is not registered on the network. + // + // * 'NonAssociatedColdKey': + // - The hotkey we are delegating is not owned by the calling coldkey. + // + // * 'InvalidTransaction': + // - The delegate is setting a take which is not lower than the previous. + // + #[pallet::call_index(65)] + #[pallet::weight((0, DispatchClass::Normal, Pays::No))] + pub fn decrease_take(origin: OriginFor, hotkey: T::AccountId, take: u16) -> DispatchResult { + Self::do_decrease_take(origin, hotkey, take) + } + + // --- Allows delegates to increase its take value. This call is rate-limited. + // + // # Args: + // * 'origin': (::Origin): + // - The signature of the caller's coldkey. + // + // * 'hotkey' (T::AccountId): + // - The hotkey we are delegating (must be owned by the coldkey.) + // + // * 'take' (u16): + // - The new stake proportion that this hotkey takes from delegations. + // The new value can be between 0 and 11_796 and should be strictly + // greater than the previous value. It T is the new value (rational number), + // the the parameter is calculated as [65535 * T]. For example, 1% would be + // [0.01 * 65535] = [655.35] = 655 + // + // # Event: + // * TakeDecreased; + // - On successfully setting a decreased take for this hotkey. + // + // # Raises: + // * 'NotRegistered': + // - The hotkey we are delegating is not registered on the network. + // + // * 'NonAssociatedColdKey': + // - The hotkey we are delegating is not owned by the calling coldkey. + // + // * 'InvalidTransaction': + // - The delegate is setting a take which is not lower than the previous. + // + #[pallet::call_index(66)] + #[pallet::weight((0, DispatchClass::Normal, Pays::No))] + pub fn increase_take(origin: OriginFor, hotkey: T::AccountId, take: u16) -> DispatchResult { + Self::do_decrease_take(origin, hotkey, take) + } + // --- Adds stake to a hotkey. The call is made from the // coldkey account linked in the hotkey. // Only the associated coldkey is allowed to make staking and @@ -1774,7 +1862,6 @@ pub mod pallet { pub fn get_priority_set_weights(hotkey: &T::AccountId, netuid: u16) -> u64 { if Uids::::contains_key(netuid, &hotkey) { let uid = Self::get_uid_for_net_and_hotkey(netuid, &hotkey.clone()).unwrap(); - let _stake = Self::get_total_stake_for_hotkey(&hotkey); let current_block_number: u64 = Self::get_current_block_as_u64(); let default_priority: u64 = current_block_number - Self::get_last_update_for_uid(netuid, uid as u16); diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index ed25f3542a..243f09f6df 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -156,8 +156,8 @@ pub fn migrate_create_root_network() -> Weight { // Empty senate members entirely, they will be filled by by registrations // on the subnet. for hotkey_i in T::SenateMembers::members().iter() { - T::TriumvirateInterface::remove_votes(&hotkey_i).unwrap(); - T::SenateMembers::remove_member(&hotkey_i).unwrap(); + let _ = T::TriumvirateInterface::remove_votes(&hotkey_i); + let _ = T::SenateMembers::remove_member(&hotkey_i); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index b6950de3d3..29c016ba7b 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -1,5 +1,4 @@ use super::*; - use frame_support::pallet_prelude::DispatchResultWithPostInfo; use frame_support::storage::IterableStorageDoubleMap; use sp_core::{Get, H256, U256}; diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 20d28aba04..474f193d6e 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -9,6 +9,7 @@ use frame_support::{ Imbalance, }, }; +use sp_core::Get; impl Pallet { // ---- The implementation for the extrinsic become_delegate: signals that this hotkey allows delegated stake. @@ -42,7 +43,7 @@ impl Pallet { hotkey: T::AccountId, take: u16, ) -> dispatch::DispatchResult { - // --- 1. We check the coldkey signuture. + // --- 1. We check the coldkey signature. let coldkey = ensure_signed(origin)?; log::info!( "do_become_delegate( origin:{:?} hotkey:{:?}, take:{:?} )", @@ -51,38 +52,40 @@ impl Pallet { take ); - // --- 2. Ensure we are delegating an known key. - ensure!( - Self::hotkey_account_exists(&hotkey), - Error::::NotRegistered - ); - + // --- 2. Ensure we are delegating a known key. // --- 3. Ensure that the coldkey is the owner. + Self::do_take_checks(&coldkey, &hotkey)?; + + // --- 4. Ensure take is within the 0 ..= InitialDefaultTake (18%) range + let max_take = T::InitialDefaultTake::get(); ensure!( - Self::coldkey_owns_hotkey(&coldkey, &hotkey), - Error::::NonAssociatedColdKey + take <= max_take, + Error::::InvalidTake ); - // --- 4. Ensure we are not already a delegate (dont allow changing delegate take.) + // --- 5. Ensure we are not already a delegate (dont allow changing delegate take here.) ensure!( !Self::hotkey_is_delegate(&hotkey), Error::::AlreadyDelegate ); - // --- 5. Ensure we don't exceed tx rate limit + // --- 6. Ensure we don't exceed tx rate limit let block: u64 = Self::get_current_block_as_u64(); ensure!( !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), Error::::TxRateLimitExceeded ); - // --- 6. Delegate the key. + // --- 7. Delegate the key. Self::delegate_hotkey(&hotkey, take); // Set last block for rate limiting Self::set_last_tx_block(&coldkey, block); - // --- 7. Emit the staking event. + // Also, set last block for take increase rate limiting + Self::set_last_tx_block_delegate_take(&coldkey, block); + + // --- 8. Emit the staking event. log::info!( "DelegateAdded( coldkey:{:?}, hotkey:{:?}, take:{:?} )", coldkey, @@ -91,6 +94,154 @@ impl Pallet { ); Self::deposit_event(Event::DelegateAdded(coldkey, hotkey, take)); + // --- 9. Ok and return. + Ok(()) + } + + // ---- The implementation for the extrinsic decrease_take + // + // # Args: + // * 'origin': (::RuntimeOrigin): + // - The signature of the caller's coldkey. + // + // * 'hotkey' (T::AccountId): + // - The hotkey we are delegating (must be owned by the coldkey.) + // + // * 'take' (u16): + // - The stake proportion that this hotkey takes from delegations. + // + // # Event: + // * TakeDecreased; + // - On successfully setting a decreased take for this hotkey. + // + // # Raises: + // * 'NotRegistered': + // - The hotkey we are delegating is not registered on the network. + // + // * 'NonAssociatedColdKey': + // - The hotkey we are delegating is not owned by the calling coldket. + // + pub fn do_decrease_take( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + take: u16, + ) -> dispatch::DispatchResult { + // --- 1. We check the coldkey signature. + let coldkey = ensure_signed(origin)?; + log::info!( + "do_decrease_take( origin:{:?} hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + + // --- 2. Ensure we are delegating a known key. + // Ensure that the coldkey is the owner. + Self::do_take_checks(&coldkey, &hotkey)?; + + // --- 3. Ensure we are always strictly decreasing, never increasing take + let current_take: u16 = Delegates::::get(&hotkey); + ensure!( + take < current_take, + Error::::InvalidTake + ); + + // --- 4. Set the new take value. + Delegates::::insert(hotkey.clone(), take); + + // --- 5. Emit the take value. + log::info!( + "TakeDecreased( coldkey:{:?}, hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + Self::deposit_event(Event::TakeDecreased(coldkey, hotkey, take)); + + // --- 6. Ok and return. + Ok(()) + } + + // ---- The implementation for the extrinsic increase_take + // + // # Args: + // * 'origin': (::RuntimeOrigin): + // - The signature of the caller's coldkey. + // + // * 'hotkey' (T::AccountId): + // - The hotkey we are delegating (must be owned by the coldkey.) + // + // * 'take' (u16): + // - The stake proportion that this hotkey takes from delegations. + // + // # Event: + // * TakeDecreased; + // - On successfully setting a decreased take for this hotkey. + // + // # Raises: + // * 'NotRegistered': + // - The hotkey we are delegating is not registered on the network. + // + // * 'NonAssociatedColdKey': + // - The hotkey we are delegating is not owned by the calling coldket. + // + // * 'TxRateLimitExceeded': + // - Thrown if key has hit transaction rate limit + // + pub fn do_increase_take( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + take: u16, + ) -> dispatch::DispatchResult { + // --- 1. We check the coldkey signature. + let coldkey = ensure_signed(origin)?; + log::info!( + "do_increase_take( origin:{:?} hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + + // --- 2. Ensure we are delegating a known key. + // Ensure that the coldkey is the owner. + Self::do_take_checks(&coldkey, &hotkey)?; + + // --- 3. Ensure we are strinctly increasing take + let current_take: u16 = Delegates::::get(&hotkey); + ensure!( + take > current_take, + Error::::InvalidTake + ); + + // --- 4. Ensure take is within the 0 ..= InitialDefaultTake (18%) range + let max_take = T::InitialDefaultTake::get(); + ensure!( + take <= max_take, + Error::::InvalidTake + ); + + // --- 5. Enforce the rate limit (independently on do_add_stake rate limits) + let block: u64 = Self::get_current_block_as_u64(); + ensure!( + !Self::exceeds_tx_delegate_take_rate_limit(Self::get_last_tx_block_delegate_take(&coldkey), block), + Error::::TxRateLimitExceeded + ); + + // Set last block for rate limiting + Self::set_last_tx_block_delegate_take(&coldkey, block); + + // --- 6. Set the new take value. + Delegates::::insert(hotkey.clone(), take); + + // --- 7. Emit the take value. + log::info!( + "TakeIncreased( coldkey:{:?}, hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + Self::deposit_event(Event::TakeIncreased(coldkey, hotkey, take)); + // --- 8. Ok and return. Ok(()) } @@ -460,6 +611,12 @@ impl Pallet { return Owner::::get(hotkey); } + // Returns the hotkey take + // + pub fn get_hotkey_take(hotkey: &T::AccountId) -> u16 { + Delegates::::get(hotkey) + } + // Returns true if the hotkey account has been created. // pub fn hotkey_account_exists(hotkey: &T::AccountId) -> bool { diff --git a/pallets/subtensor/src/types.rs b/pallets/subtensor/src/types.rs index 4b1a7b8e2d..beea5f416c 100644 --- a/pallets/subtensor/src/types.rs +++ b/pallets/subtensor/src/types.rs @@ -1,5 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; +#[allow(unused_imports)] use alloc::vec::Vec; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index e7d972f3b7..c51d12d24e 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -297,6 +297,28 @@ impl Pallet { GlobalStakeWeight::::put(global_stake_weight); } + // ======================== + // ===== Take checks ====== + // ======================== + pub fn do_take_checks( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + ) -> Result<(), Error> { + // Ensure we are delegating a known key. + ensure!( + Self::hotkey_account_exists(hotkey), + Error::::NotRegistered + ); + + // Ensure that the coldkey is the owner. + ensure!( + Self::coldkey_owns_hotkey(coldkey, hotkey), + Error::::NonAssociatedColdKey + ); + + Ok(()) + } + // ======================== // ==== Rate Limiting ===== // ======================== @@ -306,6 +328,12 @@ impl Pallet { pub fn get_last_tx_block(key: &T::AccountId) -> u64 { LastTxBlock::::get(key) } + pub fn set_last_tx_block_delegate_take(key: &T::AccountId, block: u64) { + LastTxBlockDelegateTake::::insert(key, block) + } + pub fn get_last_tx_block_delegate_take(key: &T::AccountId) -> u64 { + LastTxBlockDelegateTake::::get(key) + } pub fn exceeds_tx_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { let rate_limit: u64 = Self::get_tx_rate_limit(); if rate_limit == 0 || prev_tx_block == 0 { @@ -314,6 +342,14 @@ impl Pallet { return current_block - prev_tx_block <= rate_limit; } + pub fn exceeds_tx_delegate_take_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { + let rate_limit: u64 = Self::get_tx_delegate_take_rate_limit(); + if rate_limit == 0 || prev_tx_block == 0 { + return false; + } + + return current_block - prev_tx_block <= rate_limit; + } // ======================== // === Token Management === @@ -352,6 +388,13 @@ impl Pallet { TxRateLimit::::put(tx_rate_limit); Self::deposit_event(Event::TxRateLimitSet(tx_rate_limit)); } + pub fn get_tx_delegate_take_rate_limit() -> u64 { + TxDelegateTakeRateLimit::::get() + } + pub fn set_tx_delegate_take_rate_limit(tx_rate_limit: u64) { + TxDelegateTakeRateLimit::::put(tx_rate_limit); + Self::deposit_event(Event::TxDelegateTakeRateLimitSet(tx_rate_limit)); + } pub fn get_serving_rate_limit(netuid: u16) -> u64 { ServingRateLimit::::get(netuid) diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 5bcd988c31..0c2c32f498 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -1,3 +1,4 @@ +#![allow(non_snake_case, non_camel_case_types)] use frame_support::traits::Hash; use frame_support::{ assert_ok, parameter_types, @@ -123,10 +124,11 @@ parameter_types! { pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; - pub const InitialDefaultTake: u16 = 11_796; // 18% honest number. + pub const InitialDefaultTake: u16 = 32_767; // 50% for tests (18% honest number is used in production (see runtime)) pub const InitialWeightsVersionKey: u16 = 0; pub const InitialServingRateLimit: u64 = 0; // No limit. pub const InitialTxRateLimit: u64 = 0; // Disable rate limit for testing + pub const InitialTxDelegateTakeRateLimit: u64 = 1; // 1 block take rate limit for testing pub const InitialBurn: u64 = 0; pub const InitialMinBurn: u64 = 0; pub const InitialMaxBurn: u64 = 1_000_000_000; @@ -153,7 +155,7 @@ parameter_types! { pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; - pub const InitialTargetStakesPerInterval: u16 = 1; + pub const InitialTargetStakesPerInterval: u16 = 2; } // Configure collective pallet for council @@ -339,6 +341,7 @@ impl pallet_subtensor::Config for Test { type InitialMinDifficulty = InitialMinDifficulty; type InitialServingRateLimit = InitialServingRateLimit; type InitialTxRateLimit = InitialTxRateLimit; + type InitialTxDelegateTakeRateLimit = InitialTxDelegateTakeRateLimit; type InitialBurn = InitialBurn; type InitialMaxBurn = InitialMaxBurn; type InitialMinBurn = InitialMinBurn; diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 38f2587693..14e65a32e1 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -785,13 +785,13 @@ fn test_issance_bounds() { // Simulate 100 halvings convergence to 21M. Note that the total issuance never reaches 21M because of rounding errors. // We converge to 20_999_999_989_500_000 (< 1 TAO away). let n_halvings: usize = 100; - let mut total_issuance: u64 = 0; - for i in 0..n_halvings { - let block_emission_10_500_000x: u64 = - SubtensorModule::get_block_emission_for_issuance(total_issuance).unwrap() - * 10_500_000; - total_issuance += block_emission_10_500_000x; - } + let total_issuance = (0..n_halvings) + .into_iter() + .fold(0, |total, _| { + let block_emission_10_500_000x: u64 = + SubtensorModule::get_block_emission_for_issuance(total).unwrap() * 10_500_000; + total + block_emission_10_500_000x + }); assert_eq!(total_issuance, 20_999_999_989_500_000); }) } @@ -868,7 +868,7 @@ fn test_get_emission_across_entire_issuance_range() { let total_supply: u64 = pallet_subtensor::TotalSupply::::get(); let original_emission: u64 = pallet_subtensor::DefaultBlockEmission::::get(); let halving_issuance: u64 = total_supply / 2; - let mut step: usize = original_emission as usize; + let step: usize = original_emission as usize; for issuance in (0..=total_supply).step_by(step) { SubtensorModule::set_total_issuance(issuance); @@ -889,7 +889,7 @@ fn test_get_emission_across_entire_issuance_range() { "Issuance: {}", issuance_f64 ); - // step = expected_emission as usize; + assert_eq!(expected_emission <= usize::MAX as u64, true); } }); } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index d40c7aef56..9dbeedaa5b 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1826,11 +1826,12 @@ fn test_full_with_delegating() { step_block(3); + // 100% take is not a valid business case, changing the rest of this test to 50% assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(coldkey3), hotkey3, - u16::MAX - )); // Full take. + u16::MAX / 2 + )); // 50% take. assert_ok!(SubtensorModule::add_subnet_stake( <::RuntimeOrigin>::signed(coldkey0), hotkey3, @@ -1870,20 +1871,20 @@ fn test_full_with_delegating() { SubtensorModule::emit_inflation_through_hotkey_account(&hotkey3, netuid, 0, 1000); assert_eq!( SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey3, netuid), - 1000 - ); + 1125 + ); // 1000 + 50% * 1000 * 1000/4000 = 1125 assert_eq!( SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey3, netuid), - 1000 - ); + 1125 + ); // 1000 + 50% * 1000 * 1000/4000 = 1125 assert_eq!( SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey3, netuid), - 1000 - ); + 1125 + ); // 1000 + 50% * 1000 * 1000/4000 = 1125 assert_eq!( SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey3, &hotkey3, netuid), - 2000 - ); + 1625 + ); // 1000 + 125 * 3 + 1000 * 1000/4000 = 1625 assert_eq!(SubtensorModule::get_total_stake(), 11_500); // before + 1_000 = 10_500 + 1_000 = 11_500 }); } @@ -2730,6 +2731,431 @@ fn test_faucet_ok() { }); } +// Verify that InitialDefaultTake is between 50% and u16::MAX-1, this is important for other tests +#[test] +fn test_delegate_take_limit() { + new_test_ext(1).execute_with(|| { + assert_eq!(InitialDefaultTake::get() >= u16::MAX/2, true); + assert_eq!(InitialDefaultTake::get() <= u16::MAX-1, true); + }); +} + +// Verify delegate take can be decreased +#[test] +fn test_delegate_take_can_be_decreased() { + new_test_ext(1).execute_with(|| { + // Make account + let hotkey0 = U256::from(1); + let coldkey0 = U256::from(3); + + // Add balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + + // Register the neuron to a new network + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + + // Coldkey / hotkey 0 become delegates with 5% take + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 2 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 2); + + // Coldkey / hotkey 0 decreases take to 10% + assert_ok!(SubtensorModule::do_decrease_take( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 10 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 10); + }); +} + +// Verify delegate take can not be increased with do_decrease_take +#[test] +fn test_delegate_take_can_not_be_increased_with_decrease_take() { + new_test_ext(1).execute_with(|| { + // Make account + let hotkey0 = U256::from(1); + let coldkey0 = U256::from(3); + + // Add balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + + // Register the neuron to a new network + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + + // Coldkey / hotkey 0 become delegates with 5% take + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 20 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 20); + + // Coldkey / hotkey 0 tries to increase take to 10% + assert_eq!( + SubtensorModule::do_decrease_take( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 10 + ), + Err(Error::::InvalidTake.into()) + ); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 20); + }); +} + +// Verify delegate take can be increased +#[test] +fn test_delegate_take_can_be_increased() { + new_test_ext(1).execute_with(|| { + // Make account + let hotkey0 = U256::from(1); + let coldkey0 = U256::from(3); + + // Add balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + + // Register the neuron to a new network + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + + // Coldkey / hotkey 0 become delegates with 5% take + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 20 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 20); + + step_block(1 + InitialTxDelegateTakeRateLimit::get() as u16); + + // Coldkey / hotkey 0 decreases take to 10% + assert_ok!(SubtensorModule::do_increase_take( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 10 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 10); + }); +} + +// Verify delegate take can not be decreased with increase_take +#[test] +fn test_delegate_take_can_not_be_decreased_with_increase_take() { + new_test_ext(1).execute_with(|| { + // Make account + let hotkey0 = U256::from(1); + let coldkey0 = U256::from(3); + + // Add balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + + // Register the neuron to a new network + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + + // Coldkey / hotkey 0 become delegates with 10% take + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 10 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 10); + + // Coldkey / hotkey 0 tries to decrease take to 5% + assert_eq!( + SubtensorModule::do_increase_take( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 20 + ), + Err(Error::::InvalidTake.into()) + ); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 10); + }); +} + +// Verify delegate take can be increased up to InitialDefaultTake (18%) +#[test] +fn test_delegate_take_can_be_increased_to_limit() { + new_test_ext(1).execute_with(|| { + // Make account + let hotkey0 = U256::from(1); + let coldkey0 = U256::from(3); + + // Add balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + + // Register the neuron to a new network + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + + // Coldkey / hotkey 0 become delegates with 10% take + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 10 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 10); + + step_block(1 + InitialTxDelegateTakeRateLimit::get() as u16); + + // Coldkey / hotkey 0 tries to increase take to InitialDefaultTake+1 + assert_ok!(SubtensorModule::do_increase_take( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + InitialDefaultTake::get() + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), InitialDefaultTake::get()); + }); +} + +// Verify delegate take can not be set above InitialDefaultTake +#[test] +fn test_delegate_take_can_not_be_set_beyond_limit() { + new_test_ext(1).execute_with(|| { + // Make account + let hotkey0 = U256::from(1); + let coldkey0 = U256::from(3); + + // Add balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + + // Register the neuron to a new network + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + let before = SubtensorModule::get_hotkey_take(&hotkey0); + + // Coldkey / hotkey 0 attempt to become delegates with take above maximum + // (Disable this check if InitialDefaultTake is u16::MAX) + if InitialDefaultTake::get() != u16::MAX { + assert_eq!( + SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + InitialDefaultTake::get()+1 + ), + Err(Error::::InvalidTake.into()) + ); + } + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), before); + }); +} + +// Verify delegate take can not be increased above InitialDefaultTake (18%) +#[test] +fn test_delegate_take_can_not_be_increased_beyond_limit() { + new_test_ext(1).execute_with(|| { + // Make account + let hotkey0 = U256::from(1); + let coldkey0 = U256::from(3); + + // Add balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + + // Register the neuron to a new network + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + + // Coldkey / hotkey 0 become delegates with 10% take + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 10 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 10); + + // Coldkey / hotkey 0 tries to increase take to InitialDefaultTake+1 + // (Disable this check if InitialDefaultTake is u16::MAX) + if InitialDefaultTake::get() != u16::MAX { + assert_eq!( + SubtensorModule::do_increase_take( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + InitialDefaultTake::get()+1 + ), + Err(Error::::InvalidTake.into()) + ); + } + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 10); + }); +} + +// Verify delegate take affects emission distribution +#[test] +fn test_delegate_take_affects_distribution() { + new_test_ext(1).execute_with(|| { + let netuid = 1; + // Make two accounts. + let hotkey0 = U256::from(1); + let hotkey1 = U256::from(2); + + let coldkey0 = U256::from(3); + let coldkey1 = U256::from(4); + SubtensorModule::set_max_registrations_per_block(netuid, 4); + SubtensorModule::set_max_allowed_uids(netuid, 10); // Allow at least 10 to be registered at once, so no unstaking occurs + + // Add balances. + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey1, 100000); + + // Register the 2 neurons to a new network. + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + register_ok_neuron(netuid, hotkey1, coldkey1, 987907); + + // Stake 100 from coldkey/hotkey 0 + assert_ok!(SubtensorModule::add_subnet_stake( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + netuid, + 100 + )); + assert_eq!( + SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0, netuid), + 100 + ); + + // Coldkey / hotkey 0 become delegates with 50% take + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 2 + )); + + // Hotkey 1 adds 100 delegated stake to coldkey/hotkey 0 + assert_eq!( + SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0, netuid), + 0 + ); + assert_eq!(SubtensorModule::get_total_stake(), 100); + assert_ok!(SubtensorModule::add_subnet_stake( + <::RuntimeOrigin>::signed(coldkey1), + hotkey0, + netuid, + 100 + )); + assert_eq!( + SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0, netuid), + 100 + ); + assert_eq!(SubtensorModule::get_total_stake(), 200); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 200); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 0); + + // Lets emit inflation through this new key with distributed ownership. + // We will emit 0 server emission (which should go in-full to the owner of the hotkey). + // We will emit 400 validator emission, which should be distributed in-part to the nominators. + // + // Total initial stake is 200 + // Delegate's initial stake is 100, which is 50% of total stake + // => Delegate will receive 50% of emission (200) + 50% take (100) of nominator reward (200) + SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, netuid, 0, 400); + assert_eq!( + SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0, netuid), + 400 + ); // 100 + 50% * 400 + 50% * 200 = 400 + }); +} + +// Verify changing delegate take also changes emission distribution +#[test] +fn test_changing_delegate_take_changes_distribution() { + new_test_ext(1).execute_with(|| { + let netuid = 1; + // Make two accounts. + let hotkey0 = U256::from(1); + let hotkey1 = U256::from(2); + + let coldkey0 = U256::from(3); + let coldkey1 = U256::from(4); + SubtensorModule::set_max_registrations_per_block(netuid, 4); + SubtensorModule::set_max_allowed_uids(netuid, 10); // Allow at least 10 to be registered at once, so no unstaking occurs + + // Add balances. + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey1, 100000); + + // Register the 2 neurons to a new network. + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + register_ok_neuron(netuid, hotkey1, coldkey1, 987907); + + // Stake 100 from coldkey/hotkey 0 + assert_ok!(SubtensorModule::add_subnet_stake( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + netuid, + 100 + )); + assert_eq!( + SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0, netuid), + 100 + ); + + // Coldkey / hotkey 0 become delegates with 50% take + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 2 + )); + + // Hotkey 1 adds 100 delegated stake to coldkey/hotkey 0 + assert_eq!( + SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0, netuid), + 0 + ); + assert_eq!(SubtensorModule::get_total_stake(), 100); + assert_ok!(SubtensorModule::add_subnet_stake( + <::RuntimeOrigin>::signed(coldkey1), + hotkey0, + netuid, + 100 + )); + assert_eq!( + SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0, netuid), + 100 + ); + assert_eq!(SubtensorModule::get_total_stake(), 200); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 200); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 0); + + // Coldkey / hotkey 0 decrease take to 10% + assert_ok!(SubtensorModule::do_decrease_take( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 10 + )); + + // Lets emit inflation through this new key with distributed ownership. + // We will emit 0 server emission (which should go in-full to the owner of the hotkey). + // We will emit 400 validator emission, which should be distributed in-part to the nominators. + // + // Total initial stake is 200 + // Delegate's initial stake is 100, which is 50% of total stake + // => Delegate will receive 50% of emission (200) + 10% take (20) of nominator reward (200) + SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, netuid, 0, 400); + assert_eq!( + SubtensorModule::get_subnet_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0, netuid), + 320 + ); // 100 + 50% * 400 + 10% * 200 = 320 + }); +} + #[test] // Set up 32 subnets with a total of 1024 nodes each, and a root network with 1024 nodes. // Each subnet has a total of 1024 nodes, and a root network has 1024 nodes. @@ -3097,3 +3523,51 @@ fn test_substake_increases_stake_of_only_targeted_neuron() { } }); } + +// Test rate-limiting on increase_take +#[test] +fn test_rate_limits_enforced_on_increase_take() { + new_test_ext(1).execute_with(|| { + // Make account + let hotkey0 = U256::from(1); + let coldkey0 = U256::from(3); + + // Add balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 100000); + + // Register the neuron to a new network + let netuid = 1; + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + + // Coldkey / hotkey 0 become delegates with 5% take + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 20 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 20); + + // Coldkey / hotkey 0 increases take to 10% + assert_eq!( + SubtensorModule::do_increase_take( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 10 + ), + Err(Error::::TxRateLimitExceeded.into()) + ); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 20); + + step_block(1 + InitialTxDelegateTakeRateLimit::get() as u16); + + // Can increase after waiting + assert_ok!(SubtensorModule::do_increase_take( + <::RuntimeOrigin>::signed(coldkey0), + hotkey0, + u16::MAX / 10 + )); + assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 10); + + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index aa07114d7f..2e7b58e6f8 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -649,7 +649,7 @@ parameter_types! { pub const SubtensorInitialMaxRegistrationsPerBlock: u16 = 1; pub const SubtensorInitialPruningScore : u16 = u16::MAX; pub const SubtensorInitialBondsMovingAverage: u64 = 900_000; - pub const SubtensorInitialDefaultTake: u16 = 11_796; // 18% honest number. + pub const SubtensorInitialDefaultTake: u16 = 11_796; // 18% honest number (65535 * 0.18 = 11_796) pub const SubtensorInitialWeightsVersionKey: u64 = 0; pub const SubtensorInitialMinDifficulty: u64 = 10_000_000; pub const SubtensorInitialMaxDifficulty: u64 = u64::MAX / 4; @@ -658,6 +658,7 @@ parameter_types! { pub const SubtensorInitialMinBurn: u64 = 1_000_000_000; // 1 tao pub const SubtensorInitialMaxBurn: u64 = 100_000_000_000; // 100 tao pub const SubtensorInitialTxRateLimit: u64 = 1000; + pub const SubtensorInitialTxDelegateTakeRateLimit: u64 = 216000; // 30 days at 12 seconds per block pub const SubtensorInitialRAORecycledForRegistration: u64 = 0; // 0 rao pub const SubtensorInitialSenateRequiredStakePercentage: u64 = 1; // 1 percent of total stake pub const SubtensorInitialNetworkImmunity: u64 = 7 * 7200; @@ -707,6 +708,7 @@ impl pallet_subtensor::Config for Runtime { type InitialMaxBurn = SubtensorInitialMaxBurn; type InitialMinBurn = SubtensorInitialMinBurn; type InitialTxRateLimit = SubtensorInitialTxRateLimit; + type InitialTxDelegateTakeRateLimit = SubtensorInitialTxDelegateTakeRateLimit; type InitialRAORecycledForRegistration = SubtensorInitialRAORecycledForRegistration; type InitialSenateRequiredStakePercentage = SubtensorInitialSenateRequiredStakePercentage; type InitialNetworkImmunityPeriod = SubtensorInitialNetworkImmunity; @@ -745,6 +747,10 @@ impl SubtensorModule::set_tx_rate_limit(rate_limit); } + fn set_tx_delegate_take_rate_limit(rate_limit: u64) { + SubtensorModule::set_tx_delegate_take_rate_limit(rate_limit); + } + fn set_serving_rate_limit(netuid: u16, rate_limit: u64) { SubtensorModule::set_serving_rate_limit(netuid, rate_limit); }