Skip to content
Merged
2 changes: 1 addition & 1 deletion docs/rust-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
25 changes: 25 additions & 0 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
}
13 changes: 13 additions & 0 deletions pallets/subtensor/src/staking/account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use super::*;

impl<T: Config> Pallet<T> {
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(())
}
}
6 changes: 5 additions & 1 deletion pallets/subtensor/src/staking/decrease_take.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ impl<T: Config> Pallet<T> {
// --- 4. Set the new take value.
Delegates::<T>::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,
Expand Down
4 changes: 2 additions & 2 deletions pallets/subtensor/src/staking/increase_take.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ impl<T: Config> Pallet<T> {
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::<T>::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::<T>::insert(hotkey.clone(), take);
Expand Down
1 change: 1 addition & 0 deletions pallets/subtensor/src/staking/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
pub mod account;
pub mod add_stake;
pub mod decrease_take;
pub mod helpers;
Expand Down
64 changes: 64 additions & 0 deletions pallets/subtensor/src/tests/staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Test>::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::<Test>::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(|| {
Expand Down
66 changes: 66 additions & 0 deletions pallets/subtensor/src/tests/staking2.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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);
});
}
2 changes: 1 addition & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading