From 1215d33fecc9954335e6dbdeb0a55232c1a863b3 Mon Sep 17 00:00:00 2001 From: Nikolay Stankov Date: Mon, 20 Oct 2025 13:54:28 -0400 Subject: [PATCH 1/4] feature/fix-btcli-subnet-0-swap --- bittensor_cli/cli.py | 36 +++++++- tests/unit_tests/test_cli.py | 172 ++++++++++++++++++++++++++++++++++- 2 files changed, 205 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index a73aa2e81..26b853948 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2237,15 +2237,49 @@ def wallet_swap_hotkey( - Make sure that your original key pair (coldkeyA, hotkeyA) is already registered. - Make sure that you use a newly created hotkeyB in this command. A hotkeyB that is already registered cannot be used in this command. - - You can specify the netuid for which you want to swap the hotkey for. If it is not defined, the swap will be initiated for all subnets. + - If NO netuid is specified, the swap will be initiated for ALL subnets (recommended for most users). + - If a SPECIFIC netuid is specified (e.g., --netuid 1), the swap will only affect that particular subnet. + - WARNING: Using --netuid 0 will ONLY move child hotkey delegation mappings on root, NOT a full swap. Use without --netuid for full swap. - Finally, note that this command requires a fee of 1 TAO for recycling and this fee is taken from your wallet (coldkeyA). EXAMPLE + Full swap across all subnets (recommended): + [green]$[/green] btcli wallet swap_hotkey destination_hotkey_name --wallet-name your_wallet_name --wallet-hotkey original_hotkey + + Swap for a specific subnet only: [green]$[/green] btcli wallet swap_hotkey destination_hotkey_name --wallet-name your_wallet_name --wallet-hotkey original_hotkey --netuid 1 """ netuid = get_optional_netuid(netuid, all_netuids) self.verbosity_handler(quiet, verbose, json_output) + + # Warning for netuid 0 - only moves child hotkey delegation, not a full swap + if netuid == 0 and prompt: + console.print( + "\n[bold yellow]⚠️ WARNING: Using --netuid 0 for swap_hotkey[/bold yellow]\n" + ) + console.print( + "[yellow]Specifying --netuid 0 will ONLY move the child hotkey delegation mappings " + "on the root network.[/yellow]\n" + ) + console.print( + "[yellow]This is NOT a full hotkey swap across all subnets.[/yellow]\n" + ) + console.print( + "[bold cyan]💡 Recommended:[/bold cyan] Use this command [bold]WITHOUT[/bold] the --netuid flag " + "to swap your hotkey across ALL subnets:\n" + ) + console.print( + f"[bold green]btcli wallet swap_hotkey {destination_hotkey_name or ''} " + f"--wallet-name {wallet_name or ''} " + f"--wallet-hotkey {wallet_hotkey or ''}[/bold green]\n" + ) + + if not Confirm.ask( + "Are you SURE you want to proceed with --netuid 0 (only child hotkey delegation)?", + default=False + ): + return original_wallet = self.wallet_ask( wallet_name, wallet_path, diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py index b7933e226..68d95d30b 100644 --- a/tests/unit_tests/test_cli.py +++ b/tests/unit_tests/test_cli.py @@ -1,8 +1,8 @@ import pytest import typer -from bittensor_cli.cli import parse_mnemonic -from unittest.mock import AsyncMock, patch, MagicMock +from bittensor_cli.cli import parse_mnemonic, CLIManager +from unittest.mock import AsyncMock, patch, MagicMock, Mock def test_parse_mnemonic(): @@ -51,3 +51,171 @@ async def test_subnet_sets_price_correctly(): ) mock_price_method.assert_awaited_once_with(netuid=1, block_hash=None) assert subnet_info.price == mock_price + + +@patch('bittensor_cli.cli.Confirm') +@patch('bittensor_cli.cli.console') +def test_swap_hotkey_netuid_0_warning_with_prompt(mock_console, mock_confirm): + """ + Test that swap_hotkey shows warning when netuid=0 and prompt=True, + and exits when user declines confirmation + """ + # Setup + cli_manager = CLIManager() + mock_confirm.ask.return_value = False # User declines + + # Mock dependencies to prevent actual execution + with ( + patch.object(cli_manager, 'verbosity_handler'), + patch.object(cli_manager, 'wallet_ask') as mock_wallet_ask, + patch.object(cli_manager, 'initialize_chain'), + ): + mock_wallet_ask.return_value = Mock() + + # Call the method with netuid=0 and prompt=True + result = cli_manager.wallet_swap_hotkey( + wallet_name="test_wallet", + wallet_path="/tmp/test", + wallet_hotkey="old_hotkey", + netuid=0, + all_netuids=False, + network=None, + destination_hotkey_name="new_hotkey", + quiet=False, + verbose=False, + prompt=True, + json_output=False + ) + + # Assert: Warning was displayed + assert mock_console.print.call_count >= 4 # Multiple warning messages + warning_calls = [str(call) for call in mock_console.print.call_args_list] + assert any("WARNING" in str(call) for call in warning_calls) + assert any("child hotkey delegation" in str(call) for call in warning_calls) + + # Assert: User was asked to confirm + mock_confirm.ask.assert_called_once() + confirm_message = mock_confirm.ask.call_args[0][0] + assert "SURE" in confirm_message + assert "netuid 0" in confirm_message + + # Assert: Function returned None (early exit) because user declined + assert result is None + + +@patch('bittensor_cli.cli.Confirm') +@patch('bittensor_cli.cli.console') +def test_swap_hotkey_netuid_0_proceeds_with_confirmation(mock_console, mock_confirm): + """ + Test that swap_hotkey proceeds when netuid=0 and user confirms + """ + # Setup + cli_manager = CLIManager() + mock_confirm.ask.return_value = True # User confirms + + # Mock dependencies + with ( + patch.object(cli_manager, 'verbosity_handler'), + patch.object(cli_manager, 'wallet_ask') as mock_wallet_ask, + patch.object(cli_manager, 'initialize_chain'), + patch.object(cli_manager, '_run_command') as mock_run_command, + ): + mock_wallet = Mock() + mock_wallet_ask.return_value = mock_wallet + + # Call the method + cli_manager.wallet_swap_hotkey( + wallet_name="test_wallet", + wallet_path="/tmp/test", + wallet_hotkey="old_hotkey", + netuid=0, + all_netuids=False, + network=None, + destination_hotkey_name="new_hotkey", + quiet=False, + verbose=False, + prompt=True, + json_output=False + ) + + # Assert: Warning was shown and confirmed + mock_confirm.ask.assert_called_once() + + # Assert: Command execution proceeded + mock_run_command.assert_called_once() + + +@patch('bittensor_cli.cli.console') +def test_swap_hotkey_netuid_0_no_warning_with_no_prompt(mock_console): + """ + Test that swap_hotkey does NOT show warning when prompt=False + """ + # Setup + cli_manager = CLIManager() + + # Mock dependencies + with ( + patch.object(cli_manager, 'verbosity_handler'), + patch.object(cli_manager, 'wallet_ask') as mock_wallet_ask, + patch.object(cli_manager, 'initialize_chain'), + patch.object(cli_manager, '_run_command'), + ): + mock_wallet = Mock() + mock_wallet_ask.return_value = mock_wallet + + # Call the method with prompt=False + cli_manager.wallet_swap_hotkey( + wallet_name="test_wallet", + wallet_path="/tmp/test", + wallet_hotkey="old_hotkey", + netuid=0, + all_netuids=False, + network=None, + destination_hotkey_name="new_hotkey", + quiet=False, + verbose=False, + prompt=False, # No prompt + json_output=False + ) + + # Assert: No warning messages about netuid 0 + warning_calls = [str(call) for call in mock_console.print.call_args_list] + assert not any("WARNING" in str(call) and "netuid 0" in str(call) for call in warning_calls) + + +@patch('bittensor_cli.cli.console') +def test_swap_hotkey_netuid_1_no_warning(mock_console): + """ + Test that swap_hotkey does NOT show warning when netuid != 0 + """ + # Setup + cli_manager = CLIManager() + + # Mock dependencies + with ( + patch.object(cli_manager, 'verbosity_handler'), + patch.object(cli_manager, 'wallet_ask') as mock_wallet_ask, + patch.object(cli_manager, 'initialize_chain'), + patch.object(cli_manager, '_run_command'), + ): + mock_wallet = Mock() + mock_wallet_ask.return_value = mock_wallet + + # Call the method with netuid=1 + cli_manager.wallet_swap_hotkey( + wallet_name="test_wallet", + wallet_path="/tmp/test", + wallet_hotkey="old_hotkey", + netuid=1, # Not 0 + all_netuids=False, + network=None, + destination_hotkey_name="new_hotkey", + quiet=False, + verbose=False, + prompt=True, + json_output=False + ) + + # Assert: No warning messages about netuid 0 + warning_calls = [str(call) for call in mock_console.print.call_args_list] + assert not any("WARNING" in str(call) and "netuid 0" in str(call) for call in warning_calls) From adbb410b0f3ebacc466755e8c9669b66823c1946 Mon Sep 17 00:00:00 2001 From: Nikolay Stankov Date: Mon, 20 Oct 2025 14:03:15 -0400 Subject: [PATCH 2/4] feature/fix-btcli-subnet-0-swap --- CHANGELOG.md | 3 +++ bittensor_cli/cli.py | 15 +++++---------- tests/unit_tests/test_cli.py | 11 ++++++----- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f96f8ad..d59961b83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ **Full Changelog**: https://github.com/opentensor/btcli/compare/v9.13.1...v9.14.0 +# 9.13.2 /2025-10-20 +* Add warning and confirmation for `wallet swap_hotkey --netuid 0` to prevent accidental misuse. Using `--netuid 0` only swaps the hotkey on the root network (netuid 0) and does NOT move child hotkey delegation mappings. This is not a full swap across all subnets. Updated documentation and added comprehensive unit tests to clarify proper usage. + # 9.13.1 /2025-10-14 * Fix for complicated (user_liquidity_enabled) hyperparams by @thewhaleking in https://github.com/opentensor/btcli/pull/652 * Fixes a number of type annotations by @thewhaleking in https://github.com/opentensor/btcli/pull/653 diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 2597bc9a0..67b043f6b 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2288,7 +2288,7 @@ def wallet_swap_hotkey( - Make sure that you use a newly created hotkeyB in this command. A hotkeyB that is already registered cannot be used in this command. - If NO netuid is specified, the swap will be initiated for ALL subnets (recommended for most users). - If a SPECIFIC netuid is specified (e.g., --netuid 1), the swap will only affect that particular subnet. - - WARNING: Using --netuid 0 will ONLY move child hotkey delegation mappings on root, NOT a full swap. Use without --netuid for full swap. + - WARNING: Using --netuid 0 will ONLY swap on the root network (netuid 0), NOT a full swap across all subnets. Use without --netuid for full swap. - Finally, note that this command requires a fee of 1 TAO for recycling and this fee is taken from your wallet (coldkeyA). EXAMPLE @@ -2302,21 +2302,16 @@ def wallet_swap_hotkey( netuid = get_optional_netuid(netuid, all_netuids) self.verbosity_handler(quiet, verbose, json_output) - # Warning for netuid 0 - only moves child hotkey delegation, not a full swap + # Warning for netuid 0 - only swaps on root network, not a full swap if netuid == 0 and prompt: console.print( "\n[bold yellow]⚠️ WARNING: Using --netuid 0 for swap_hotkey[/bold yellow]\n" ) console.print( - "[yellow]Specifying --netuid 0 will ONLY move the child hotkey delegation mappings " - "on the root network.[/yellow]\n" + "[yellow]Specifying --netuid 0 will ONLY swap the hotkey on the root network (netuid 0).[/yellow]\n" ) console.print( - "[yellow]This is NOT a full hotkey swap across all subnets.[/yellow]\n" - ) - console.print( - "[bold cyan]💡 Recommended:[/bold cyan] Use this command [bold]WITHOUT[/bold] the --netuid flag " - "to swap your hotkey across ALL subnets:\n" + "[yellow]It will NOT move child hotkey delegation mappings on root.[/yellow]\n" ) console.print( f"[bold green]btcli wallet swap_hotkey {destination_hotkey_name or ''} " @@ -2325,7 +2320,7 @@ def wallet_swap_hotkey( ) if not Confirm.ask( - "Are you SURE you want to proceed with --netuid 0 (only child hotkey delegation)?", + "Are you SURE you want to proceed with --netuid 0 (only root network swap)?", default=False ): return diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py index 68d95d30b..f80fea352 100644 --- a/tests/unit_tests/test_cli.py +++ b/tests/unit_tests/test_cli.py @@ -87,17 +87,18 @@ def test_swap_hotkey_netuid_0_warning_with_prompt(mock_console, mock_confirm): json_output=False ) - # Assert: Warning was displayed - assert mock_console.print.call_count >= 4 # Multiple warning messages + # Assert: Warning was displayed (4 console.print calls for the warning) + assert mock_console.print.call_count >= 4 warning_calls = [str(call) for call in mock_console.print.call_args_list] - assert any("WARNING" in str(call) for call in warning_calls) - assert any("child hotkey delegation" in str(call) for call in warning_calls) + assert any("WARNING" in str(call) and "netuid 0" in str(call) for call in warning_calls) + assert any("root network" in str(call) for call in warning_calls) + assert any("NOT move child hotkey delegation" in str(call) for call in warning_calls) # Assert: User was asked to confirm mock_confirm.ask.assert_called_once() confirm_message = mock_confirm.ask.call_args[0][0] assert "SURE" in confirm_message - assert "netuid 0" in confirm_message + assert "netuid 0" in confirm_message or "root network" in confirm_message # Assert: Function returned None (early exit) because user declined assert result is None From e449831d8126597d4069e3214fd5bffd75245189 Mon Sep 17 00:00:00 2001 From: Nikolay Stankov Date: Mon, 20 Oct 2025 14:12:15 -0400 Subject: [PATCH 3/4] ruff --- PR.md | 107 +++++++++++++++++++++++++++++++++++ bittensor_cli/cli.py | 6 +- tests/unit_tests/test_cli.py | 92 ++++++++++++++++-------------- 3 files changed, 160 insertions(+), 45 deletions(-) create mode 100644 PR.md diff --git a/PR.md b/PR.md new file mode 100644 index 000000000..ac70c7f2b --- /dev/null +++ b/PR.md @@ -0,0 +1,107 @@ +# Add Warning for `swap_hotkey --netuid 0` to Prevent Accidental Misuse + +## Problem + +Users accidentally using `btcli wallet swap_hotkey --netuid 0` may not realize that this command only swaps the hotkey on the root network (netuid 0) and does NOT move child hotkey delegation mappings. This is NOT a full hotkey swap across all subnets, which can lead to unexpected behavior and confusion. + +### What Happens with `--netuid 0` +- ❌ Only swaps on root network (netuid 0) +- ❌ Does NOT move child hotkey delegation mappings +- ❌ Does NOT swap across all other subnets + +### Expected Behavior (Without `--netuid`) +- ✅ Swaps hotkey across ALL subnets +- ✅ Complete hotkey migration +- ✅ Recommended for most users + +## Solution + +This PR adds a prominent warning and confirmation prompt when users attempt to use `--netuid 0` with the `swap_hotkey` command. The warning clearly explains: +1. What `--netuid 0` actually does (only root network swap) +2. What it does NOT do (move child delegation, full swap) +3. The recommended command to use instead + +## Changes Made + +### 1. `bittensor_cli/cli.py` +- Added warning check for `netuid == 0` in `wallet_swap_hotkey()` method +- Warning displays: + - Clear explanation of `--netuid 0` behavior + - Statement that it won't move child hotkey delegation mappings + - Recommended command without `--netuid` flag + - Requires explicit user confirmation to proceed +- Updated docstring to clarify netuid behavior +- Only shows warning when `prompt=True` (skips for automated scripts) + +### 2. `tests/unit_tests/test_cli.py` +Added 4 comprehensive unit tests: +- `test_swap_hotkey_netuid_0_warning_with_prompt`: Verifies warning is shown and user can decline +- `test_swap_hotkey_netuid_0_proceeds_with_confirmation`: Verifies operation proceeds when user confirms +- `test_swap_hotkey_netuid_0_no_warning_with_no_prompt`: Verifies no warning when `--no-prompt` is used +- `test_swap_hotkey_netuid_1_no_warning`: Verifies no warning for other netuids + +### 3. `CHANGELOG.md` +- Documented the fix in version 9.13.2 + +## Warning Example + +When a user runs: +```bash +btcli wallet swap_hotkey new_hotkey \ + --wallet-name wallet \ + --wallet-hotkey old_hotkey \ + --netuid 0 +``` + +They now see: +``` +⚠️ WARNING: Using --netuid 0 for swap_hotkey + +Specifying --netuid 0 will ONLY swap the hotkey on the root network (netuid 0). + +It will NOT move child hotkey delegation mappings on root. + +btcli wallet swap_hotkey new_hotkey --wallet-name wallet --wallet-hotkey old_hotkey + +Are you SURE you want to proceed with --netuid 0 (only root network swap)? [y/n] (n): +``` + +## Testing + +### Unit Tests +All 4 new unit tests pass: +```bash +pytest tests/unit_tests/test_cli.py -k "swap_hotkey" -v +# ✅ 4 passed +``` + +### Manual CLI Testing +- ✅ `--netuid 0` with prompt: Shows warning, requires confirmation +- ✅ `--netuid 1` (normal): No warning shown +- ✅ `--netuid 0 --no-prompt`: No warning (automation support) +- ✅ No `--netuid` flag: No warning (recommended usage) + +## Behavior + +| Command | Warning Shown? | Behavior | +|---------|----------------|----------| +| `swap_hotkey ... --netuid 0` | ⚠️ YES | Shows warning, requires confirmation | +| `swap_hotkey ... --netuid 1` | ✅ NO | Swaps on netuid 1 only (as expected) | +| `swap_hotkey ...` (no netuid) | ✅ NO | Full swap across all subnets (recommended) | +| `swap_hotkey ... --netuid 0 --no-prompt` | ✅ NO | Skips warning for automation | + +## Related Context + +This fix addresses user confusion discovered when a user accidentally ran `swap_hotkey` with `--netuid 0` expecting a full swap but only got a root network swap without child delegation movement. The warning prevents this mistake and guides users to the correct usage. + +## Checklist + +- [x] Code follows existing patterns in the codebase +- [x] Uses `Confirm.ask()` consistent with other warnings +- [x] Added comprehensive unit tests (4 tests) +- [x] Updated CHANGELOG.md +- [x] Updated docstring documentation +- [x] Tested manually with CLI +- [x] Warning only shows when appropriate (not for automation) +- [x] No new linting errors introduced + diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 67b043f6b..e54a5e690 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2301,7 +2301,7 @@ def wallet_swap_hotkey( """ netuid = get_optional_netuid(netuid, all_netuids) self.verbosity_handler(quiet, verbose, json_output) - + # Warning for netuid 0 - only swaps on root network, not a full swap if netuid == 0 and prompt: console.print( @@ -2318,10 +2318,10 @@ def wallet_swap_hotkey( f"--wallet-name {wallet_name or ''} " f"--wallet-hotkey {wallet_hotkey or ''}[/bold green]\n" ) - + if not Confirm.ask( "Are you SURE you want to proceed with --netuid 0 (only root network swap)?", - default=False + default=False, ): return original_wallet = self.wallet_ask( diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py index f80fea352..a17ed8406 100644 --- a/tests/unit_tests/test_cli.py +++ b/tests/unit_tests/test_cli.py @@ -53,8 +53,8 @@ async def test_subnet_sets_price_correctly(): assert subnet_info.price == mock_price -@patch('bittensor_cli.cli.Confirm') -@patch('bittensor_cli.cli.console') +@patch("bittensor_cli.cli.Confirm") +@patch("bittensor_cli.cli.console") def test_swap_hotkey_netuid_0_warning_with_prompt(mock_console, mock_confirm): """ Test that swap_hotkey shows warning when netuid=0 and prompt=True, @@ -66,12 +66,12 @@ def test_swap_hotkey_netuid_0_warning_with_prompt(mock_console, mock_confirm): # Mock dependencies to prevent actual execution with ( - patch.object(cli_manager, 'verbosity_handler'), - patch.object(cli_manager, 'wallet_ask') as mock_wallet_ask, - patch.object(cli_manager, 'initialize_chain'), + patch.object(cli_manager, "verbosity_handler"), + patch.object(cli_manager, "wallet_ask") as mock_wallet_ask, + patch.object(cli_manager, "initialize_chain"), ): mock_wallet_ask.return_value = Mock() - + # Call the method with netuid=0 and prompt=True result = cli_manager.wallet_swap_hotkey( wallet_name="test_wallet", @@ -84,28 +84,32 @@ def test_swap_hotkey_netuid_0_warning_with_prompt(mock_console, mock_confirm): quiet=False, verbose=False, prompt=True, - json_output=False + json_output=False, ) # Assert: Warning was displayed (4 console.print calls for the warning) assert mock_console.print.call_count >= 4 warning_calls = [str(call) for call in mock_console.print.call_args_list] - assert any("WARNING" in str(call) and "netuid 0" in str(call) for call in warning_calls) + assert any( + "WARNING" in str(call) and "netuid 0" in str(call) for call in warning_calls + ) assert any("root network" in str(call) for call in warning_calls) - assert any("NOT move child hotkey delegation" in str(call) for call in warning_calls) - + assert any( + "NOT move child hotkey delegation" in str(call) for call in warning_calls + ) + # Assert: User was asked to confirm mock_confirm.ask.assert_called_once() confirm_message = mock_confirm.ask.call_args[0][0] assert "SURE" in confirm_message assert "netuid 0" in confirm_message or "root network" in confirm_message - + # Assert: Function returned None (early exit) because user declined assert result is None -@patch('bittensor_cli.cli.Confirm') -@patch('bittensor_cli.cli.console') +@patch("bittensor_cli.cli.Confirm") +@patch("bittensor_cli.cli.console") def test_swap_hotkey_netuid_0_proceeds_with_confirmation(mock_console, mock_confirm): """ Test that swap_hotkey proceeds when netuid=0 and user confirms @@ -113,17 +117,17 @@ def test_swap_hotkey_netuid_0_proceeds_with_confirmation(mock_console, mock_conf # Setup cli_manager = CLIManager() mock_confirm.ask.return_value = True # User confirms - + # Mock dependencies with ( - patch.object(cli_manager, 'verbosity_handler'), - patch.object(cli_manager, 'wallet_ask') as mock_wallet_ask, - patch.object(cli_manager, 'initialize_chain'), - patch.object(cli_manager, '_run_command') as mock_run_command, + patch.object(cli_manager, "verbosity_handler"), + patch.object(cli_manager, "wallet_ask") as mock_wallet_ask, + patch.object(cli_manager, "initialize_chain"), + patch.object(cli_manager, "_run_command") as mock_run_command, ): mock_wallet = Mock() mock_wallet_ask.return_value = mock_wallet - + # Call the method cli_manager.wallet_swap_hotkey( wallet_name="test_wallet", @@ -136,34 +140,34 @@ def test_swap_hotkey_netuid_0_proceeds_with_confirmation(mock_console, mock_conf quiet=False, verbose=False, prompt=True, - json_output=False + json_output=False, ) - + # Assert: Warning was shown and confirmed mock_confirm.ask.assert_called_once() - + # Assert: Command execution proceeded mock_run_command.assert_called_once() -@patch('bittensor_cli.cli.console') +@patch("bittensor_cli.cli.console") def test_swap_hotkey_netuid_0_no_warning_with_no_prompt(mock_console): """ Test that swap_hotkey does NOT show warning when prompt=False """ # Setup cli_manager = CLIManager() - + # Mock dependencies with ( - patch.object(cli_manager, 'verbosity_handler'), - patch.object(cli_manager, 'wallet_ask') as mock_wallet_ask, - patch.object(cli_manager, 'initialize_chain'), - patch.object(cli_manager, '_run_command'), + patch.object(cli_manager, "verbosity_handler"), + patch.object(cli_manager, "wallet_ask") as mock_wallet_ask, + patch.object(cli_manager, "initialize_chain"), + patch.object(cli_manager, "_run_command"), ): mock_wallet = Mock() mock_wallet_ask.return_value = mock_wallet - + # Call the method with prompt=False cli_manager.wallet_swap_hotkey( wallet_name="test_wallet", @@ -176,32 +180,34 @@ def test_swap_hotkey_netuid_0_no_warning_with_no_prompt(mock_console): quiet=False, verbose=False, prompt=False, # No prompt - json_output=False + json_output=False, ) - + # Assert: No warning messages about netuid 0 warning_calls = [str(call) for call in mock_console.print.call_args_list] - assert not any("WARNING" in str(call) and "netuid 0" in str(call) for call in warning_calls) + assert not any( + "WARNING" in str(call) and "netuid 0" in str(call) for call in warning_calls + ) -@patch('bittensor_cli.cli.console') +@patch("bittensor_cli.cli.console") def test_swap_hotkey_netuid_1_no_warning(mock_console): """ Test that swap_hotkey does NOT show warning when netuid != 0 """ # Setup cli_manager = CLIManager() - + # Mock dependencies with ( - patch.object(cli_manager, 'verbosity_handler'), - patch.object(cli_manager, 'wallet_ask') as mock_wallet_ask, - patch.object(cli_manager, 'initialize_chain'), - patch.object(cli_manager, '_run_command'), + patch.object(cli_manager, "verbosity_handler"), + patch.object(cli_manager, "wallet_ask") as mock_wallet_ask, + patch.object(cli_manager, "initialize_chain"), + patch.object(cli_manager, "_run_command"), ): mock_wallet = Mock() mock_wallet_ask.return_value = mock_wallet - + # Call the method with netuid=1 cli_manager.wallet_swap_hotkey( wallet_name="test_wallet", @@ -214,9 +220,11 @@ def test_swap_hotkey_netuid_1_no_warning(mock_console): quiet=False, verbose=False, prompt=True, - json_output=False + json_output=False, ) - + # Assert: No warning messages about netuid 0 warning_calls = [str(call) for call in mock_console.print.call_args_list] - assert not any("WARNING" in str(call) and "netuid 0" in str(call) for call in warning_calls) + assert not any( + "WARNING" in str(call) and "netuid 0" in str(call) for call in warning_calls + ) From 51c373869a7b43698835d6b3330015e92ed7b4a1 Mon Sep 17 00:00:00 2001 From: Nikolay Stankov Date: Mon, 20 Oct 2025 14:13:11 -0400 Subject: [PATCH 4/4] rm pr.md --- PR.md | 107 ---------------------------------------------------------- 1 file changed, 107 deletions(-) delete mode 100644 PR.md diff --git a/PR.md b/PR.md deleted file mode 100644 index ac70c7f2b..000000000 --- a/PR.md +++ /dev/null @@ -1,107 +0,0 @@ -# Add Warning for `swap_hotkey --netuid 0` to Prevent Accidental Misuse - -## Problem - -Users accidentally using `btcli wallet swap_hotkey --netuid 0` may not realize that this command only swaps the hotkey on the root network (netuid 0) and does NOT move child hotkey delegation mappings. This is NOT a full hotkey swap across all subnets, which can lead to unexpected behavior and confusion. - -### What Happens with `--netuid 0` -- ❌ Only swaps on root network (netuid 0) -- ❌ Does NOT move child hotkey delegation mappings -- ❌ Does NOT swap across all other subnets - -### Expected Behavior (Without `--netuid`) -- ✅ Swaps hotkey across ALL subnets -- ✅ Complete hotkey migration -- ✅ Recommended for most users - -## Solution - -This PR adds a prominent warning and confirmation prompt when users attempt to use `--netuid 0` with the `swap_hotkey` command. The warning clearly explains: -1. What `--netuid 0` actually does (only root network swap) -2. What it does NOT do (move child delegation, full swap) -3. The recommended command to use instead - -## Changes Made - -### 1. `bittensor_cli/cli.py` -- Added warning check for `netuid == 0` in `wallet_swap_hotkey()` method -- Warning displays: - - Clear explanation of `--netuid 0` behavior - - Statement that it won't move child hotkey delegation mappings - - Recommended command without `--netuid` flag - - Requires explicit user confirmation to proceed -- Updated docstring to clarify netuid behavior -- Only shows warning when `prompt=True` (skips for automated scripts) - -### 2. `tests/unit_tests/test_cli.py` -Added 4 comprehensive unit tests: -- `test_swap_hotkey_netuid_0_warning_with_prompt`: Verifies warning is shown and user can decline -- `test_swap_hotkey_netuid_0_proceeds_with_confirmation`: Verifies operation proceeds when user confirms -- `test_swap_hotkey_netuid_0_no_warning_with_no_prompt`: Verifies no warning when `--no-prompt` is used -- `test_swap_hotkey_netuid_1_no_warning`: Verifies no warning for other netuids - -### 3. `CHANGELOG.md` -- Documented the fix in version 9.13.2 - -## Warning Example - -When a user runs: -```bash -btcli wallet swap_hotkey new_hotkey \ - --wallet-name wallet \ - --wallet-hotkey old_hotkey \ - --netuid 0 -``` - -They now see: -``` -⚠️ WARNING: Using --netuid 0 for swap_hotkey - -Specifying --netuid 0 will ONLY swap the hotkey on the root network (netuid 0). - -It will NOT move child hotkey delegation mappings on root. - -btcli wallet swap_hotkey new_hotkey --wallet-name wallet --wallet-hotkey old_hotkey - -Are you SURE you want to proceed with --netuid 0 (only root network swap)? [y/n] (n): -``` - -## Testing - -### Unit Tests -All 4 new unit tests pass: -```bash -pytest tests/unit_tests/test_cli.py -k "swap_hotkey" -v -# ✅ 4 passed -``` - -### Manual CLI Testing -- ✅ `--netuid 0` with prompt: Shows warning, requires confirmation -- ✅ `--netuid 1` (normal): No warning shown -- ✅ `--netuid 0 --no-prompt`: No warning (automation support) -- ✅ No `--netuid` flag: No warning (recommended usage) - -## Behavior - -| Command | Warning Shown? | Behavior | -|---------|----------------|----------| -| `swap_hotkey ... --netuid 0` | ⚠️ YES | Shows warning, requires confirmation | -| `swap_hotkey ... --netuid 1` | ✅ NO | Swaps on netuid 1 only (as expected) | -| `swap_hotkey ...` (no netuid) | ✅ NO | Full swap across all subnets (recommended) | -| `swap_hotkey ... --netuid 0 --no-prompt` | ✅ NO | Skips warning for automation | - -## Related Context - -This fix addresses user confusion discovered when a user accidentally ran `swap_hotkey` with `--netuid 0` expecting a full swap but only got a root network swap without child delegation movement. The warning prevents this mistake and guides users to the correct usage. - -## Checklist - -- [x] Code follows existing patterns in the codebase -- [x] Uses `Confirm.ask()` consistent with other warnings -- [x] Added comprehensive unit tests (4 tests) -- [x] Updated CHANGELOG.md -- [x] Updated docstring documentation -- [x] Tested manually with CLI -- [x] Warning only shows when appropriate (not for automation) -- [x] No new linting errors introduced -