Skip to content

Commit 47ad71e

Browse files
committed
Add unstake all call validation in signed ext
1 parent 698a3d7 commit 47ad71e

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

pallets/subtensor/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2165,6 +2165,20 @@ where
21652165
Self::get_priority_staking(who, hotkey, *amount_unstaked),
21662166
)
21672167
}
2168+
Some(Call::unstake_all { hotkey }) => {
2169+
// Fully validate the user input
2170+
Self::result_to_validity(
2171+
Pallet::<T>::validate_unstake_all(who, hotkey, false),
2172+
Self::get_priority_vanilla(),
2173+
)
2174+
}
2175+
Some(Call::unstake_all_alpha { hotkey }) => {
2176+
// Fully validate the user input
2177+
Self::result_to_validity(
2178+
Pallet::<T>::validate_unstake_all(who, hotkey, true),
2179+
Self::get_priority_vanilla(),
2180+
)
2181+
}
21682182
Some(Call::remove_stake_limit {
21692183
hotkey,
21702184
netuid,

pallets/subtensor/src/staking/stake_utils.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,47 @@ impl<T: Config> Pallet<T> {
995995
Ok(())
996996
}
997997

998+
/// Validate if unstake_all can be executed
999+
///
1000+
pub fn validate_unstake_all(
1001+
coldkey: &T::AccountId,
1002+
hotkey: &T::AccountId,
1003+
only_alpha: bool,
1004+
) -> Result<(), Error<T>> {
1005+
// Get all netuids (filter out root)
1006+
let subnets: Vec<u16> = Self::get_all_subnet_netuids();
1007+
1008+
// Ensure that the hotkey account exists this is only possible through registration.
1009+
ensure!(
1010+
Self::hotkey_account_exists(hotkey),
1011+
Error::<T>::HotKeyAccountNotExists
1012+
);
1013+
1014+
let mut unstaking_any = false;
1015+
for netuid in subnets.iter() {
1016+
if !SubtokenEnabled::<T>::get(netuid) {
1017+
continue;
1018+
}
1019+
1020+
if only_alpha && (*netuid == Self::get_root_netuid()) {
1021+
continue;
1022+
}
1023+
1024+
// Get user's stake in this subnet
1025+
let alpha = Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, *netuid);
1026+
1027+
if Self::validate_remove_stake(coldkey, hotkey, *netuid, alpha, alpha, false).is_ok()
1028+
{
1029+
unstaking_any = true;
1030+
}
1031+
}
1032+
1033+
// If no unstaking happens, return error
1034+
ensure!(unstaking_any, Error::<T>::AmountTooLow);
1035+
1036+
Ok(())
1037+
}
1038+
9981039
/// Validate stake transition user input
9991040
/// That works for move_stake, transfer_stake, and swap_stake
10001041
///

pallets/subtensor/src/tests/staking.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3841,6 +3841,57 @@ fn test_unstake_low_liquidity_validate() {
38413841
});
38423842
}
38433843

3844+
#[test]
3845+
fn test_unstake_all_validate() {
3846+
// Testing the signed extension validate function
3847+
// correctly filters the `unstake_all` transaction.
3848+
3849+
new_test_ext(0).execute_with(|| {
3850+
let subnet_owner_coldkey = U256::from(1001);
3851+
let subnet_owner_hotkey = U256::from(1002);
3852+
let hotkey = U256::from(2);
3853+
let coldkey = U256::from(3);
3854+
let amount_staked = DefaultMinStake::<Test>::get() * 10 + DefaultStakingFee::<Test>::get();
3855+
3856+
let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey);
3857+
SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey);
3858+
SubtensorModule::add_balance_to_coldkey_account(&coldkey, amount_staked);
3859+
3860+
// Simulate stake for hotkey
3861+
SubnetTAO::<Test>::insert(netuid, u64::MAX / 1000);
3862+
SubnetAlphaIn::<Test>::insert(netuid, u64::MAX / 1000);
3863+
SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, amount_staked, 0);
3864+
3865+
// Set the liquidity at lowest possible value so that all staking requests fail
3866+
SubnetTAO::<Test>::insert(
3867+
netuid,
3868+
DefaultMinimumPoolLiquidity::<Test>::get().to_num::<u64>(),
3869+
);
3870+
SubnetAlphaIn::<Test>::insert(
3871+
netuid,
3872+
DefaultMinimumPoolLiquidity::<Test>::get().to_num::<u64>(),
3873+
);
3874+
3875+
// unstake_all call
3876+
let call = RuntimeCall::SubtensorModule(SubtensorCall::unstake_all { hotkey });
3877+
3878+
let info: DispatchInfo =
3879+
DispatchInfoOf::<<Test as frame_system::Config>::RuntimeCall>::default();
3880+
3881+
let extension = SubtensorSignedExtension::<Test>::new();
3882+
// Submit to the signed extension validate function
3883+
let result_no_stake = extension.validate(&coldkey, &call.clone(), &info, 10);
3884+
3885+
// Should fail due to insufficient stake
3886+
assert_err!(
3887+
result_no_stake,
3888+
TransactionValidityError::Invalid(InvalidTransaction::Custom(
3889+
CustomTransactionError::StakeAmountTooLow.into()
3890+
))
3891+
);
3892+
});
3893+
}
3894+
38443895
#[test]
38453896
fn test_max_amount_add_root() {
38463897
new_test_ext(0).execute_with(|| {

0 commit comments

Comments
 (0)