diff --git a/docs/rust-setup.md b/docs/rust-setup.md index 1527d5b8bb..346b424a08 100644 --- a/docs/rust-setup.md +++ b/docs/rust-setup.md @@ -24,7 +24,7 @@ Use a terminal shell to execute the following commands: ```bash sudo apt update # May prompt for location information -sudo apt install -y git clang curl libssl-dev llvm libudev-dev +sudo apt install -y git clang curl libssl-dev llvm libudev-dev make pkg-config protobuf-compiler ``` ### Arch Linux diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 4a8c985c70..7bc9e986e0 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1383,8 +1383,7 @@ pub mod pallet { /// /// # Arguments /// * `origin` - The origin of the call, which must be the root account. - /// * `precompile_id` - The identifier of the EVM precompile to toggle. - /// * `enabled` - The new enablement state of the precompile. + /// * `alpha` - The new moving alpha value for the SubnetMovingAlpha. /// /// # Errors /// * `BadOrigin` - If the caller is not the root account. diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 3875477a6f..bbd52e00d3 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1882,5 +1882,30 @@ mod dispatches { allow_partial, ) } + + /// Attempts to associate a hotkey with a coldkey. + /// + /// # Arguments + /// * `origin` - The origin of the transaction, which must be signed by the coldkey that owns the `hotkey`. + /// * `hotkey` - The hotkey to associate with the coldkey. + /// + /// # Note + /// Will charge based on the weight even if the hotkey is already associated with a coldkey. + #[pallet::call_index(91)] + #[pallet::weight(( + Weight::from_parts(3_000_000, 0).saturating_add(T::DbWeight::get().reads_writes(3, 3)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn try_associate_hotkey( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + ) -> DispatchResult { + let coldkey = ensure_signed(origin)?; + + let _ = Self::do_try_associate_hotkey(&coldkey, &hotkey); + + Ok(()) + } } } diff --git a/pallets/subtensor/src/staking/account.rs b/pallets/subtensor/src/staking/account.rs new file mode 100644 index 0000000000..20a6ff3036 --- /dev/null +++ b/pallets/subtensor/src/staking/account.rs @@ -0,0 +1,13 @@ +use super::*; + +impl Pallet { + pub fn do_try_associate_hotkey( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + ) -> DispatchResult { + // Ensure the hotkey is not already associated with a coldkey + Self::create_account_if_non_existent(coldkey, hotkey); + + Ok(()) + } +} diff --git a/pallets/subtensor/src/staking/decrease_take.rs b/pallets/subtensor/src/staking/decrease_take.rs index 8742f809db..c227aad8a3 100644 --- a/pallets/subtensor/src/staking/decrease_take.rs +++ b/pallets/subtensor/src/staking/decrease_take.rs @@ -57,7 +57,11 @@ impl Pallet { // --- 4. Set the new take value. Delegates::::insert(hotkey.clone(), take); - // --- 5. Emit the take value. + // --- 5. Set last block for rate limiting + let block: u64 = Self::get_current_block_as_u64(); + Self::set_last_tx_block_delegate_take(&hotkey, block); + + // --- 6. Emit the take value. log::debug!( "TakeDecreased( coldkey:{:?}, hotkey:{:?}, take:{:?} )", coldkey, diff --git a/pallets/subtensor/src/staking/increase_take.rs b/pallets/subtensor/src/staking/increase_take.rs index 0218184471..349f86e7cf 100644 --- a/pallets/subtensor/src/staking/increase_take.rs +++ b/pallets/subtensor/src/staking/increase_take.rs @@ -61,14 +61,14 @@ impl Pallet { 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), + Self::get_last_tx_block_delegate_take(&hotkey), block ), Error::::DelegateTxRateLimitExceeded ); // Set last block for rate limiting - Self::set_last_tx_block_delegate_take(&coldkey, block); + Self::set_last_tx_block_delegate_take(&hotkey, block); // --- 6. Set the new take value. Delegates::::insert(hotkey.clone(), take); diff --git a/pallets/subtensor/src/staking/mod.rs b/pallets/subtensor/src/staking/mod.rs index 2b222036cf..ecf8fb8815 100644 --- a/pallets/subtensor/src/staking/mod.rs +++ b/pallets/subtensor/src/staking/mod.rs @@ -1,4 +1,5 @@ use super::*; +pub mod account; pub mod add_stake; pub mod decrease_take; pub mod helpers; diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index e5ed95f41d..5d9db9f4e8 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -1671,6 +1671,70 @@ fn test_rate_limits_enforced_on_increase_take() { }); } +// Test rate-limiting on an increase take just after a decrease take +// Prevents a Validator from decreasing take and then increasing it immediately after. +#[test] +fn test_rate_limits_enforced_on_decrease_before_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, 1, 0); + register_ok_neuron(netuid, hotkey0, coldkey0, 124124); + + // Coldkey / hotkey 0 become delegates with 9% take + Delegates::::insert(hotkey0, SubtensorModule::get_min_delegate_take() + 1); + assert_eq!( + SubtensorModule::get_hotkey_take(&hotkey0), + SubtensorModule::get_min_delegate_take() + 1 + ); + + // Decrease take + assert_ok!(SubtensorModule::do_decrease_take( + RuntimeOrigin::signed(coldkey0), + hotkey0, + SubtensorModule::get_min_delegate_take() + )); // Verify decrease + assert_eq!( + SubtensorModule::get_hotkey_take(&hotkey0), + SubtensorModule::get_min_delegate_take() + ); + + // Increase take immediately after + assert_eq!( + SubtensorModule::do_increase_take( + RuntimeOrigin::signed(coldkey0), + hotkey0, + SubtensorModule::get_min_delegate_take() + 1 + ), + Err(Error::::DelegateTxRateLimitExceeded.into()) + ); // Verify no change + assert_eq!( + SubtensorModule::get_hotkey_take(&hotkey0), + SubtensorModule::get_min_delegate_take() + ); + + step_block(1 + InitialTxDelegateTakeRateLimit::get() as u16); + + // Can increase after waiting + assert_ok!(SubtensorModule::do_increase_take( + RuntimeOrigin::signed(coldkey0), + hotkey0, + SubtensorModule::get_min_delegate_take() + 1 + )); // Verify increase + assert_eq!( + SubtensorModule::get_hotkey_take(&hotkey0), + SubtensorModule::get_min_delegate_take() + 1 + ); + }); +} + #[test] fn test_get_total_delegated_stake_after_unstaking() { new_test_ext(1).execute_with(|| { diff --git a/pallets/subtensor/src/tests/staking2.rs b/pallets/subtensor/src/tests/staking2.rs index 471d670254..d4c1145435 100644 --- a/pallets/subtensor/src/tests/staking2.rs +++ b/pallets/subtensor/src/tests/staking2.rs @@ -1,5 +1,10 @@ use super::mock::*; use crate::*; +use frame_support::{ + assert_ok, + dispatch::{GetDispatchInfo, Pays}, + weights::Weight, +}; use sp_core::U256; use substrate_fixed::types::I96F32; @@ -557,3 +562,64 @@ fn test_share_based_staking_stake_inject_stake_new() { }); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::staking2::test_try_associate_hotkey --exact --show-output --nocapture +#[test] +fn test_try_associate_hotkey() { + new_test_ext(1).execute_with(|| { + let hotkey1 = U256::from(1); + let coldkey1 = U256::from(2); + let coldkey2 = U256::from(3); + + // Check initial association + assert!(!SubtensorModule::hotkey_account_exists(&hotkey1)); + + // Associate hotkey1 with coldkey1 + assert_ok!(SubtensorModule::try_associate_hotkey( + RuntimeOrigin::signed(coldkey1), + hotkey1 + )); + + // Check that hotkey1 is associated with coldkey1 + assert!(SubtensorModule::hotkey_account_exists(&hotkey1)); + assert_eq!( + SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey1), + coldkey1 + ); + assert_ne!(SubtensorModule::get_owned_hotkeys(&coldkey1).len(), 0); + assert!(SubtensorModule::get_owned_hotkeys(&coldkey1).contains(&hotkey1)); + + // Verify this tx requires a fee + let call = + RuntimeCall::SubtensorModule(crate::Call::try_associate_hotkey { hotkey: hotkey1 }); + let dispatch_info = call.get_dispatch_info(); + // Verify tx weight > 0 + assert!(dispatch_info.weight.all_gte(Weight::from_all(0))); + // Verify pays Yes is set + assert_eq!(dispatch_info.pays_fee, Pays::Yes); + + // Check that coldkey2 is not associated with any hotkey + assert!(!SubtensorModule::get_owned_hotkeys(&coldkey2).contains(&hotkey1)); + assert_eq!(SubtensorModule::get_owned_hotkeys(&coldkey2).len(), 0); + + // Try to associate hotkey1 with coldkey2 + // Should have no effect because coldkey1 is already associated with hotkey1 + assert_ok!(SubtensorModule::try_associate_hotkey( + RuntimeOrigin::signed(coldkey2), + hotkey1 + )); + + // Check that hotkey1 is still associated with coldkey1 + assert!(SubtensorModule::hotkey_account_exists(&hotkey1)); + assert_eq!( + SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey1), + coldkey1 + ); + assert_ne!(SubtensorModule::get_owned_hotkeys(&coldkey1).len(), 0); + assert!(SubtensorModule::get_owned_hotkeys(&coldkey1).contains(&hotkey1)); + + // Check that coldkey2 is still not associated with any hotkey + assert!(!SubtensorModule::get_owned_hotkeys(&coldkey2).contains(&hotkey1)); + assert_eq!(SubtensorModule::get_owned_hotkeys(&coldkey2).len(), 0); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index fa47d5be21..a8774795b9 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -228,7 +228,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 243, + spec_version: 244, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,