From 600676f830bf05a5fc728aafd897f8285f5a82c6 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 18 Feb 2025 16:53:55 -0800 Subject: [PATCH 1/2] Adds unstaking from all hotkeys + tests --- bittensor_cli/cli.py | 32 ++- bittensor_cli/src/commands/stake/remove.py | 313 ++++++++++++++------- tests/e2e_tests/test_unstaking.py | 281 ++++++++++++++++++ 3 files changed, 523 insertions(+), 103 deletions(-) create mode 100644 tests/e2e_tests/test_unstaking.py diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 0de749e2c..76be6debe 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3221,11 +3221,24 @@ def stake_remove( else: print_error("Invalid hotkey ss58 address.") raise typer.Exit() - else: - hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from", - default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, + elif all_hotkeys: + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH], ) + else: + if not hotkey_ss58_address and not wallet_hotkey: + hotkey_or_ss58 = Prompt.ask( + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from [dim](or enter 'all' to unstake from all hotkeys)[/dim]", + default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, + ) + elif hotkey_ss58_address: + hotkey_or_ss58 = hotkey_ss58_address + else: + hotkey_or_ss58 = wallet_hotkey + if is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 wallet = self.wallet_ask( @@ -3234,6 +3247,14 @@ def stake_remove( wallet_hotkey, ask_for=[WO.NAME, WO.PATH], ) + elif hotkey_or_ss58 == "all": + all_hotkeys = True + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH], + ) else: wallet_hotkey = hotkey_or_ss58 wallet = self.wallet_ask( @@ -3249,6 +3270,9 @@ def stake_remove( subtensor=self.initialize_chain(network), hotkey_ss58_address=hotkey_ss58_address, unstake_all_alpha=unstake_all_alpha, + all_hotkeys=all_hotkeys, + include_hotkeys=include_hotkeys, + exclude_hotkeys=exclude_hotkeys, prompt=prompt, ) ) diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index a8d364b5f..048855fdc 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -49,26 +49,36 @@ async def unstake( f"Retrieving subnet data & identities from {subtensor.network}...", spinner="earth", ): - all_sn_dynamic_info_, ck_hk_identities, old_identities = await asyncio.gather( - subtensor.all_subnets(), - subtensor.fetch_coldkey_hotkey_identities(), - subtensor.get_delegate_identities(), + chain_head = await subtensor.substrate.get_chain_head() + ( + all_sn_dynamic_info_, + ck_hk_identities, + old_identities, + stake_infos, + ) = await asyncio.gather( + subtensor.all_subnets(block_hash=chain_head), + subtensor.fetch_coldkey_hotkey_identities(block_hash=chain_head), + subtensor.get_delegate_identities(block_hash=chain_head), + subtensor.get_stake_for_coldkey( + wallet.coldkeypub.ss58_address, block_hash=chain_head + ), ) all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_} if interactive: hotkeys_to_unstake_from, unstake_all_from_hk = await _unstake_selection( - subtensor, - wallet, all_sn_dynamic_info, ck_hk_identities, old_identities, + stake_infos, netuid=netuid, ) if unstake_all_from_hk: hotkey_to_unstake_all = hotkeys_to_unstake_from[0] unstake_all_alpha = Confirm.ask( - "\nUnstake [blue]all alpha stakes[/blue] and stake back to [blue]root[/blue]? (No will unstake everything)", + "\nDo you want to:\n" + "[blue]Yes[/blue]: Unstake from all subnets and automatically restake to subnet 0 (root)\n" + "[blue]No[/blue]: Unstake everything (including subnet 0)", default=True, ) return await unstake_all( @@ -96,20 +106,17 @@ async def unstake( all_hotkeys=all_hotkeys, include_hotkeys=include_hotkeys, exclude_hotkeys=exclude_hotkeys, + stake_infos=stake_infos, + identities=ck_hk_identities, + old_identities=old_identities, ) with console.status( f"Retrieving stake data from {subtensor.network}...", spinner="earth", ): - # Fetch stake balances - chain_head = await subtensor.substrate.get_chain_head() - stake_info_list = await subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - block_hash=chain_head, - ) stake_in_netuids = {} - for stake_info in stake_info_list: + for stake_info in stake_infos: if stake_info.hotkey_ss58 not in stake_in_netuids: stake_in_netuids[stake_info.hotkey_ss58] = {} stake_in_netuids[stake_info.hotkey_ss58][stake_info.netuid] = ( @@ -313,6 +320,9 @@ async def unstake_all( subtensor: "SubtensorInterface", hotkey_ss58_address: str, unstake_all_alpha: bool = False, + all_hotkeys: bool = False, + include_hotkeys: list[str] = [], + exclude_hotkeys: list[str] = [], prompt: bool = True, ) -> bool: """Unstakes all stakes from all hotkeys in all subnets.""" @@ -334,10 +344,27 @@ async def unstake_all( subtensor.all_subnets(), subtensor.get_balance(wallet.coldkeypub.ss58_address), ) - if not hotkey_ss58_address: - hotkey_ss58_address = wallet.hotkey.ss58_address + + if all_hotkeys: + hotkeys = _get_hotkeys_to_unstake( + wallet, + hotkey_ss58_address=hotkey_ss58_address, + all_hotkeys=all_hotkeys, + include_hotkeys=include_hotkeys, + exclude_hotkeys=exclude_hotkeys, + stake_infos=stake_info, + identities=ck_hk_identities, + old_identities=old_identities, + ) + elif not hotkey_ss58_address: + hotkeys = [(wallet.hotkey_str, wallet.hotkey.ss58_address)] + else: + hotkeys = [(None, hotkey_ss58_address)] + + hotkey_names = {ss58: name for name, ss58 in hotkeys if name is not None} + hotkey_ss58s = [ss58 for _, ss58 in hotkeys] stake_info = [ - stake for stake in stake_info if stake.hotkey_ss58 == hotkey_ss58_address + stake for stake in stake_info if stake.hotkey_ss58 in hotkey_ss58s ] if unstake_all_alpha: @@ -403,18 +430,7 @@ async def unstake_all( if stake.stake.rao == 0: continue - # Get hotkey identity - if hk_identity := ck_hk_identities["hotkeys"].get(stake.hotkey_ss58): - hotkey_name = hk_identity.get("identity", {}).get( - "name", "" - ) or hk_identity.get("display", "~") - hotkey_display = f"{hotkey_name}" - elif old_identity := old_identities.get(stake.hotkey_ss58): - hotkey_name = old_identity.display - hotkey_display = f"{hotkey_name}" - else: - hotkey_display = stake.hotkey_ss58 - + hotkey_display = hotkey_names.get(stake.hotkey_ss58, stake.hotkey_ss58) subnet_info = all_sn_dynamic_info.get(stake.netuid) stake_amount = stake.stake received_amount, slippage_pct, slippage_pct_float = _calculate_slippage( @@ -455,56 +471,16 @@ async def unstake_all( err_console.print("Error decrypting coldkey (possibly incorrect password)") return False - console_status = ( - ":satellite: Unstaking all Alpha stakes..." - if unstake_all_alpha - else ":satellite: Unstaking all stakes..." - ) - previous_root_stake = await subtensor.get_stake( - hotkey_ss58=hotkey_ss58_address, - coldkey_ss58=wallet.coldkeypub.ss58_address, - netuid=0, - ) - with console.status(console_status): - call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all" - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params={"hotkey": hotkey_ss58_address}, - ) - success, error_message = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=True, - wait_for_finalization=False, - ) - - if success: - success_message = ( - ":white_heavy_check_mark: [green]Successfully unstaked all stakes[/green]" - if not unstake_all_alpha - else ":white_heavy_check_mark: [green]Successfully unstaked all Alpha stakes[/green]" - ) - console.print(success_message) - new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) - console.print( - f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" - ) - if unstake_all_alpha: - root_stake = await subtensor.get_stake( - hotkey_ss58=hotkey_ss58_address, - coldkey_ss58=wallet.coldkeypub.ss58_address, - netuid=0, - ) - console.print( - f"Root Stake:\n [blue]{previous_root_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{root_stake}" - ) - return True - else: - err_console.print( - f":cross_mark: [red]Failed to unstake[/red]: {error_message}" + with console.status("Unstaking all stakes...") as status: + for hotkey_ss58 in hotkey_ss58s: + await _unstake_all_extrinsic( + wallet=wallet, + subtensor=subtensor, + hotkey_ss58=hotkey_ss58, + hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58), + unstake_all_alpha=unstake_all_alpha, + status=status, ) - return False # Extrinsics @@ -666,9 +642,7 @@ async def _safe_unstake_extrinsic( ) return else: - err_out( - f"\n{failure_prelude} with error: {format_error_message(e)}" - ) + err_out(f"\n{failure_prelude} with error: {format_error_message(e)}") return await response.process_events() @@ -709,6 +683,115 @@ async def _safe_unstake_extrinsic( ) +async def _unstake_all_extrinsic( + wallet: Wallet, + subtensor: "SubtensorInterface", + hotkey_ss58: str, + hotkey_name: str, + unstake_all_alpha: bool, + status=None, +) -> None: + """Execute an unstake all extrinsic. + + Args: + wallet: Wallet instance + subtensor: Subtensor interface + hotkey_ss58: Hotkey SS58 address + hotkey_name: Display name of the hotkey + unstake_all_alpha: Whether to unstake only alpha stakes + status: Optional status for console updates + """ + err_out = partial(print_error, status=status) + failure_prelude = ( + f":cross_mark: [red]Failed[/red] to unstake all from {hotkey_name}" + ) + + if status: + status.update( + f"\n:satellite: {'Unstaking all Alpha stakes' if unstake_all_alpha else 'Unstaking all stakes'} from {hotkey_name} ..." + ) + + block_hash = await subtensor.substrate.get_chain_head() + if unstake_all_alpha: + previous_root_stake, current_balance = await asyncio.gather( + subtensor.get_stake( + hotkey_ss58=hotkey_ss58, + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuid=0, + block_hash=block_hash, + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + ) + else: + current_balance = await subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ) + + call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all" + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params={"hotkey": hotkey_ss58}, + ) + + try: + response = await subtensor.substrate.submit_extrinsic( + extrinsic=await subtensor.substrate.create_signed_extrinsic( + call=call, + keypair=wallet.coldkey, + ), + wait_for_inclusion=True, + wait_for_finalization=False, + ) + await response.process_events() + + if not await response.is_success: + err_out( + f"{failure_prelude} with error: " + f"{format_error_message(await response.error_message)}" + ) + return + + # Fetch latest balance and stake + block_hash = await subtensor.substrate.get_chain_head() + if unstake_all_alpha: + new_root_stake, new_balance = await asyncio.gather( + subtensor.get_stake( + hotkey_ss58=hotkey_ss58, + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuid=0, + block_hash=block_hash, + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + ) + else: + new_balance = await subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ) + + success_message = ( + ":white_heavy_check_mark: [green]Finalized: Successfully unstaked all stakes[/green]" + if not unstake_all_alpha + else ":white_heavy_check_mark: [green]Finalized: Successfully unstaked all Alpha stakes[/green]" + ) + console.print(f"{success_message} from {hotkey_name}") + console.print( + f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" + ) + + if unstake_all_alpha: + console.print( + f"Root Stake for {hotkey_name}:\n [blue]{previous_root_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_root_stake}" + ) + + except Exception as e: + err_out(f"{failure_prelude} with error: {str(e)}") + + # Helpers def _calculate_slippage(subnet_info, amount: Balance) -> tuple[Balance, str, float]: """Calculate slippage and received amount for unstaking operation. @@ -737,17 +820,12 @@ def _calculate_slippage(subnet_info, amount: Balance) -> tuple[Balance, str, flo async def _unstake_selection( - subtensor: "SubtensorInterface", - wallet: Wallet, dynamic_info, identities, old_identities, + stake_infos, netuid: Optional[int] = None, ): - stake_infos = await subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address - ) - if not stake_infos: print_error("You have no stakes to unstake.") raise typer.Exit() @@ -771,16 +849,11 @@ async def _unstake_selection( hotkeys_info = [] for idx, (hotkey_ss58, netuid_stakes) in enumerate(hotkey_stakes.items()): - if hk_identity := identities["hotkeys"].get(hotkey_ss58): - hotkey_name = hk_identity.get("identity", {}).get( - "name", "" - ) or hk_identity.get("display", "~") - elif old_identity := old_identities.get(hotkey_ss58): - hotkey_name = old_identity.display - else: - hotkey_name = "~" - # TODO: Add wallet ids here. - + hotkey_name = get_hotkey_identity( + hotkey_ss58=hotkey_ss58, + identities=identities, + old_identities=old_identities, + ) hotkeys_info.append( { "index": idx, @@ -983,6 +1056,9 @@ def _get_hotkeys_to_unstake( all_hotkeys: bool, include_hotkeys: list[str], exclude_hotkeys: list[str], + stake_infos: list, + identities: dict, + old_identities: dict, ) -> list[tuple[Optional[str], str]]: """Get list of hotkeys to unstake from based on input parameters. @@ -1002,13 +1078,27 @@ def _get_hotkeys_to_unstake( if all_hotkeys: print_verbose("Unstaking from all hotkeys") - all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet) - return [ + all_hotkeys_ = get_hotkey_wallets_for_wallet(wallet=wallet) + wallet_hotkeys = [ (wallet.hotkey_str, wallet.hotkey.ss58_address) for wallet in all_hotkeys_ if wallet.hotkey_str not in exclude_hotkeys ] + wallet_hotkey_addresses = {addr for _, addr in wallet_hotkeys} + chain_hotkeys = [ + ( + get_hotkey_identity(stake_info.hotkey_ss58, identities, old_identities), + stake_info.hotkey_ss58, + ) + for stake_info in stake_infos + if ( + stake_info.hotkey_ss58 not in wallet_hotkey_addresses + and stake_info.hotkey_ss58 not in exclude_hotkeys + ) + ] + return wallet_hotkeys + chain_hotkeys + if include_hotkeys: print_verbose("Unstaking from included hotkeys") result = [] @@ -1144,3 +1234,28 @@ def _print_table_and_slippage( - [bold white]Partial unstaking[/bold white]: If True, allows unstaking up to the rate tolerance limit. If False, the entire transaction will fail if rate tolerance is exceeded.\n""" console.print(base_description + (safe_staking_description if safe_staking else "")) + + +def get_hotkey_identity( + hotkey_ss58: str, + identities: dict, + old_identities: dict, +) -> str: + """Get identity name for a hotkey from identities or old_identities. + + Args: + hotkey_ss58 (str): The hotkey SS58 address + identities (dict): Current identities from fetch_coldkey_hotkey_identities + old_identities (dict): Old identities from get_delegate_identities + + Returns: + str: Identity name or truncated address + """ + if hk_identity := identities["hotkeys"].get(hotkey_ss58): + return hk_identity.get("identity", {}).get("name", "") or hk_identity.get( + "display", "~" + ) + elif old_identity := old_identities.get(hotkey_ss58): + return old_identity.display + else: + return f"{hotkey_ss58[:4]}...{hotkey_ss58[-4:]}" diff --git a/tests/e2e_tests/test_unstaking.py b/tests/e2e_tests/test_unstaking.py new file mode 100644 index 000000000..c401654e5 --- /dev/null +++ b/tests/e2e_tests/test_unstaking.py @@ -0,0 +1,281 @@ +import re + +from bittensor_cli.src.bittensor.balances import Balance + + +def test_unstaking(local_chain, wallet_setup): + """ + Test various unstaking scenarios including partial unstake, unstake all alpha, and unstake all. + + Steps: + 1. Create wallets for Alice and Bob + 2. Create 2 subnets with Alice + 3. Register Bob in one subnet + 4. Add stake from Bob to all subnets (except 1) + 5. Remove partial stake from one subnet and verify + 6. Remove all alpha stake and verify + 7. Add stake again to both subnets + 8. Remove all stake and verify + """ + print("Testing unstaking scenarios ๐Ÿงช") + + # Create wallets for Alice and Bob + wallet_path_alice = "//Alice" + wallet_path_bob = "//Bob" + + # Setup Alice's wallet + keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( + wallet_path_alice + ) + + # Setup Bob's wallet + keypair_bob, wallet_bob, wallet_path_bob, exec_command_bob = wallet_setup( + wallet_path_bob + ) + + # Create first subnet (netuid = 2) + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--chain", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--name", + "Test Subnet 2", + "--repo", + "https://github.com/username/repo", + "--contact", + "test@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "test#1234", + "--description", + "A test subnet for e2e testing", + "--additional-info", + "Test subnet", + "--no-prompt", + ], + ) + assert "โœ… Registered subnetwork with netuid: 2" in result.stdout + + # Create second subnet (netuid = 3) + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--chain", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--name", + "Test Subnet 3", + "--repo", + "https://github.com/username/repo", + "--contact", + "test@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "test#1234", + "--description", + "A test subnet for e2e testing", + "--additional-info", + "Test subnet", + "--no-prompt", + ], + ) + assert "โœ… Registered subnetwork with netuid: 3" in result.stdout + + # Register Bob in both subnets + register_result = exec_command_bob( + command="subnets", + sub_command="register", + extra_args=[ + "--netuid", + "2", + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert "โœ… Registered" in register_result.stdout + + # Add stake to subnets + for netuid in [0, 2, 3]: + stake_result = exec_command_bob( + command="stake", + sub_command="add", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--amount", + "700", + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--partial", + "--tolerance", + "0.5", + ], + ) + assert "โœ… Finalized" in stake_result.stdout + + stake_list = exec_command_bob( + command="stake", + sub_command="list", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--verbose", + ], + ) + + cleaned_stake = [ + re.sub(r"\s+", " ", line) for line in stake_list.stdout.splitlines() + ] + inital_stake_netuid_2 = cleaned_stake[9].split("โ”‚")[3].strip().split()[0] + + # Remove partial stake from netuid 2 + partial_unstake_netuid_2 = exec_command_bob( + command="stake", + sub_command="remove", + extra_args=[ + "--netuid", + "2", + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--amount", + "100", + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--partial", + "--tolerance", + "0.5", + ], + ) + assert "โœ… Finalized" in partial_unstake_netuid_2.stdout + + # Verify partial unstake + stake_list = exec_command_bob( + command="stake", + sub_command="list", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--verbose", + ], + ) + + # Verify stake amounts after partial unstake + cleaned_stake = [ + re.sub(r"\s+", " ", line) for line in stake_list.stdout.splitlines() + ] + stake_after_unstaking_netuid_2 = cleaned_stake[9].split("โ”‚")[3].strip().split()[0] + assert Balance.from_tao(float(stake_after_unstaking_netuid_2)) <= Balance.from_tao( + float(inital_stake_netuid_2) + ) + + # Remove all alpha stakes + unstake_alpha = exec_command_bob( + command="stake", + sub_command="remove", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--chain", + "ws://127.0.0.1:9945", + "--all-alpha", + "--no-prompt", + ], + ) + assert ( + "โœ… Finalized: Successfully unstaked all Alpha stakes" in unstake_alpha.stdout + ) + + # Add stake again to subnets + for netuid in [0, 2, 3]: + stake_result = exec_command_bob( + command="stake", + sub_command="add", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--amount", + "300", + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--partial", + "--tolerance", + "0.5", + ], + ) + assert "โœ… Finalized" in stake_result.stdout + + # Remove all stakes + unstake_all = exec_command_bob( + command="stake", + sub_command="remove", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--chain", + "ws://127.0.0.1:9945", + "--all", + "--no-prompt", + ], + ) + assert "โœ… Finalized: Successfully unstaked all stakes from" in unstake_all.stdout + print("Passed unstaking tests ๐ŸŽ‰") From c2d7e7ceba5ca832af6ec34f5773a178342ded6d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 18 Feb 2025 16:58:55 -0800 Subject: [PATCH 2/2] Cleanup --- bittensor_cli/cli.py | 4 +--- tests/e2e_tests/test_unstaking.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 76be6debe..c04e33ba8 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3234,10 +3234,8 @@ def stake_remove( "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from [dim](or enter 'all' to unstake from all hotkeys)[/dim]", default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, ) - elif hotkey_ss58_address: - hotkey_or_ss58 = hotkey_ss58_address else: - hotkey_or_ss58 = wallet_hotkey + hotkey_or_ss58 = hotkey_ss58_address or wallet_hotkey if is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 diff --git a/tests/e2e_tests/test_unstaking.py b/tests/e2e_tests/test_unstaking.py index c401654e5..b885d8adf 100644 --- a/tests/e2e_tests/test_unstaking.py +++ b/tests/e2e_tests/test_unstaking.py @@ -97,7 +97,7 @@ def test_unstaking(local_chain, wallet_setup): ) assert "โœ… Registered subnetwork with netuid: 3" in result.stdout - # Register Bob in both subnets + # Register Bob in one subnet register_result = exec_command_bob( command="subnets", sub_command="register",