diff --git a/CHANGELOG.md b/CHANGELOG.md index 598bba947..56ea625bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 9.11.1 /2025-09-16 + +* Transfer not staking warning by @thewhaleking in https://github.com/opentensor/btcli/pull/618 +* update e2e tests for hyperparam freeze window by @thewhaleking in https://github.com/opentensor/btcli/pull/620 +* Corrects the stake fee calculation by @thewhaleking in https://github.com/opentensor/btcli/pull/621 +* Fix: Handle encrypted wallet hotkeys by @ibraheem-abe in https://github.com/opentensor/btcli/pull/622 +* Fix: Handle malformed wallets/files by @ibraheem-abe in https://github.com/opentensor/btcli/pull/623 +* `min_burn` now not root sudo only by @thewhaleking in https://github.com/opentensor/btcli/pull/624 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.11.0...v9.11.1 + ## 9.11.0 /2025-09-05 * Better arg naming + type annotations by @thewhaleking in https://github.com/opentensor/btcli/pull/590 * disk cache in config by @thewhaleking in https://github.com/opentensor/btcli/pull/588 diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 7160cbe25..ba96fe488 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -641,7 +641,7 @@ class WalletValidationTypes(Enum): "adjustment_interval": ("sudo_set_adjustment_interval", True), "activity_cutoff": ("sudo_set_activity_cutoff", False), "target_regs_per_interval": ("sudo_set_target_registrations_per_interval", True), - "min_burn": ("sudo_set_min_burn", True), + "min_burn": ("sudo_set_min_burn", False), "max_burn": ("sudo_set_max_burn", True), "bonds_moving_avg": ("sudo_set_bonds_moving_average", False), "max_regs_per_block": ("sudo_set_max_registrations_per_block", True), diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 0f64d8519..07fd8c906 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -1193,3 +1193,20 @@ def _fix_decoded(cls, decoded: dict) -> "MetagraphInfo": for adphk in decoded["alpha_dividends_per_hotkey"] ], ) + + +@dataclass +class SimSwapResult: + tao_amount: Balance + alpha_amount: Balance + tao_fee: Balance + alpha_fee: Balance + + @classmethod + def from_dict(cls, d: dict, netuid: int) -> "SimSwapResult": + return cls( + tao_amount=Balance.from_rao(d["tao_amount"]).set_unit(0), + alpha_amount=Balance.from_rao(d["alpha_amount"]).set_unit(netuid), + tao_fee=Balance.from_rao(d["tao_fee"]).set_unit(0), + alpha_fee=Balance.from_rao(d["alpha_fee"]).set_unit(netuid), + ) diff --git a/bittensor_cli/src/bittensor/extrinsics/transfer.py b/bittensor_cli/src/bittensor/extrinsics/transfer.py index cd435b641..ad3168a23 100644 --- a/bittensor_cli/src/bittensor/extrinsics/transfer.py +++ b/bittensor_cli/src/bittensor/extrinsics/transfer.py @@ -175,7 +175,9 @@ async def do_transfer() -> tuple[bool, str, str]: f" amount: [bright_cyan]{amount if not transfer_all else account_balance}[/bright_cyan]\n" f" from: [light_goldenrod2]{wallet.name}[/light_goldenrod2] : " f"[bright_magenta]{wallet.coldkey.ss58_address}\n[/bright_magenta]" - f" to: [bright_magenta]{destination}[/bright_magenta]\n for fee: [bright_cyan]{fee}[/bright_cyan]" + f" to: [bright_magenta]{destination}[/bright_magenta]\n for fee: [bright_cyan]{fee}[/bright_cyan]\n" + f":warning:[bright_yellow]Transferring is not the same as staking. To instead stake, use " + f"[dark_orange]btcli stake add[/dark_orange] instead[/bright_yellow]:warning:" ): return False diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index cb3b295f3..cafef0439 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -28,6 +28,7 @@ DynamicInfo, SubnetState, MetagraphInfo, + SimSwapResult, ) from bittensor_cli.src import DelegatesDetails from bittensor_cli.src.bittensor.balances import Balance, fixed_to_float @@ -1502,59 +1503,79 @@ async def get_extrinsic_fee(self, call: GenericCall, keypair: Keypair) -> Balanc fee_dict = await self.substrate.get_payment_info(call, keypair) return Balance.from_rao(fee_dict["partial_fee"]) - async def get_stake_fee( + async def sim_swap( self, - origin_hotkey_ss58: Optional[str], - origin_netuid: Optional[int], - origin_coldkey_ss58: str, - destination_hotkey_ss58: Optional[str], - destination_netuid: Optional[int], - destination_coldkey_ss58: str, + origin_netuid: int, + destination_netuid: int, amount: int, block_hash: Optional[str] = None, - ) -> Balance: + ) -> SimSwapResult: """ - Calculates the fee for a staking operation. - - :param origin_hotkey_ss58: SS58 address of source hotkey (None for new stake) - :param origin_netuid: Netuid of source subnet (None for new stake) - :param origin_coldkey_ss58: SS58 address of source coldkey - :param destination_hotkey_ss58: SS58 address of destination hotkey (None for removing stake) - :param destination_netuid: Netuid of destination subnet (None for removing stake) - :param destination_coldkey_ss58: SS58 address of destination coldkey - :param amount: Amount of stake to transfer in RAO - :param block_hash: Optional block hash at which to perform the calculation - - :return: The calculated stake fee as a Balance object - - When to use None: + Hits the SimSwap Runtime API to calculate the fee and result for a given transaction. This should be used + instead of get_stake_fee for staking fee calculations. The SimSwapResult contains the staking fees and expected + returned amounts of a given transaction. This does not include the transaction (extrinsic) fee. - 1. Adding new stake (default fee): - - origin_hotkey_ss58 = None - - origin_netuid = None - - All other fields required - - 2. Removing stake (default fee): - - destination_hotkey_ss58 = None - - destination_netuid = None - - All other fields required + Args: + origin_netuid: Netuid of the source subnet (0 if new stake) + destination_netuid: Netuid of the destination subnet + amount: Amount to transfer in Rao + block_hash: The hash of the blockchain block number for the query. - For all other operations, no None values - provide all parameters: - 3. Moving between subnets - 4. Moving between hotkeys - 5. Moving between coldkeys + Returns: + SimSwapResult object representing the result """ - - if origin_netuid is None: - origin_netuid = 0 - - fee_rate = await self.query("Swap", "FeeRate", [origin_netuid]) - fee = amount * (fee_rate / U16_MAX) - - result = Balance.from_rao(fee) - result.set_unit(origin_netuid) - - return result + block_hash = block_hash or await self.substrate.get_chain_head() + if origin_netuid > 0 and destination_netuid > 0: + # for cross-subnet moves where neither origin nor destination is root + intermediate_result_, sn_price = await asyncio.gather( + self.query_runtime_api( + "SwapRuntimeApi", + "sim_swap_alpha_for_tao", + params={"netuid": origin_netuid, "alpha": amount}, + block_hash=block_hash, + ), + self.get_subnet_price(origin_netuid, block_hash=block_hash), + ) + intermediate_result = SimSwapResult.from_dict( + intermediate_result_, origin_netuid + ) + result = SimSwapResult.from_dict( + await self.query_runtime_api( + "SwapRuntimeApi", + "sim_swap_tao_for_alpha", + params={ + "netuid": destination_netuid, + "tao": intermediate_result.tao_amount, + }, + block_hash=block_hash, + ), + destination_netuid, + ) + secondary_fee = (result.tao_fee * sn_price).set_unit(origin_netuid) + result.alpha_fee = result.alpha_fee + secondary_fee + return result + elif origin_netuid > 0: + # dynamic to tao + return SimSwapResult.from_dict( + await self.query_runtime_api( + "SwapRuntimeApi", + "sim_swap_alpha_for_tao", + params={"netuid": origin_netuid, "alpha": amount}, + block_hash=block_hash, + ), + origin_netuid, + ) + else: + # tao to dynamic or unstaked to staked tao (SN0) + return SimSwapResult.from_dict( + await self.query_runtime_api( + "SwapRuntimeApi", + "sim_swap_tao_for_alpha", + params={"netuid": destination_netuid, "tao": amount}, + block_hash=block_hash, + ), + destination_netuid, + ) async def get_scheduled_coldkey_swap( self, diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 1497470a9..80aab6916 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -266,7 +266,7 @@ def get_hotkey_wallets_for_wallet( hotkeys_path = wallet_path / wallet.name / "hotkeys" try: hotkeys = [entry.name for entry in hotkeys_path.iterdir()] - except FileNotFoundError: + except (FileNotFoundError, NotADirectoryError): hotkeys = [] for h_name in hotkeys: if h_name.endswith("pub.txt"): @@ -307,6 +307,7 @@ def get_hotkey_wallets_for_wallet( AttributeError, TypeError, KeyFileError, + ValueError, ): # usually an unrelated file like .DS_Store continue diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 6579d9767..18a507c6d 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -347,17 +347,6 @@ async def stake_extrinsic( return False remaining_wallet_balance -= amount_to_stake - # TODO this should be asyncio gathered before the for loop - stake_fee = await subtensor.get_stake_fee( - origin_hotkey_ss58=None, - origin_netuid=None, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=hotkey[1], - destination_netuid=netuid, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - amount=amount_to_stake.rao, - ) - # Calculate slippage # TODO: Update for V3, slippage calculation is significantly different in v3 # try: @@ -409,7 +398,13 @@ async def stake_extrinsic( safe_staking_=safe_staking, ) row_extension = [] - received_amount = rate * (amount_to_stake - stake_fee - extrinsic_fee) + # TODO this should be asyncio gathered before the for loop + sim_swap = await subtensor.sim_swap( + origin_netuid=0, + destination_netuid=netuid, + amount=(amount_to_stake - extrinsic_fee).rao, + ) + received_amount = sim_swap.alpha_amount # Add rows for the table base_row = [ str(netuid), # netuid @@ -418,7 +413,7 @@ async def stake_extrinsic( str(rate) + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate str(received_amount.set_unit(netuid)), # received - str(stake_fee), # fee + str(sim_swap.tao_fee), # fee str(extrinsic_fee), # str(slippage_pct), # slippage ] + row_extension diff --git a/bittensor_cli/src/commands/stake/move.py b/bittensor_cli/src/commands/stake/move.py index c72bbb41e..b4360ffdf 100644 --- a/bittensor_cli/src/commands/stake/move.py +++ b/bittensor_cli/src/commands/stake/move.py @@ -520,14 +520,10 @@ async def move_stake( "alpha_amount": amount_to_move_as_balance.rao, }, ) - stake_fee, extrinsic_fee = await asyncio.gather( - subtensor.get_stake_fee( - origin_hotkey_ss58=origin_hotkey, + sim_swap, extrinsic_fee = await asyncio.gather( + subtensor.sim_swap( origin_netuid=origin_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=destination_hotkey, destination_netuid=destination_netuid, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, amount=amount_to_move_as_balance.rao, ), subtensor.get_extrinsic_fee(call, wallet.coldkeypub), @@ -543,7 +539,9 @@ async def move_stake( origin_hotkey=origin_hotkey, destination_hotkey=destination_hotkey, amount_to_move=amount_to_move_as_balance, - stake_fee=stake_fee, + stake_fee=sim_swap.alpha_fee + if origin_netuid != 0 + else sim_swap.tao_fee, extrinsic_fee=extrinsic_fee, ) except ValueError: @@ -709,14 +707,10 @@ async def transfer_stake( "alpha_amount": amount_to_transfer.rao, }, ) - stake_fee, extrinsic_fee = await asyncio.gather( - subtensor.get_stake_fee( - origin_hotkey_ss58=origin_hotkey, + sim_swap, extrinsic_fee = await asyncio.gather( + subtensor.sim_swap( origin_netuid=origin_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=origin_hotkey, destination_netuid=dest_netuid, - destination_coldkey_ss58=dest_coldkey_ss58, amount=amount_to_transfer.rao, ), subtensor.get_extrinsic_fee(call, wallet.coldkeypub), @@ -732,7 +726,9 @@ async def transfer_stake( origin_hotkey=origin_hotkey, destination_hotkey=origin_hotkey, amount_to_move=amount_to_transfer, - stake_fee=stake_fee, + stake_fee=sim_swap.alpha_fee + if origin_netuid != 0 + else sim_swap.tao_fee, extrinsic_fee=extrinsic_fee, ) except ValueError: @@ -880,14 +876,10 @@ async def swap_stake( "alpha_amount": amount_to_swap.rao, }, ) - stake_fee, extrinsic_fee = await asyncio.gather( - subtensor.get_stake_fee( - origin_hotkey_ss58=hotkey_ss58, + sim_swap, extrinsic_fee = await asyncio.gather( + subtensor.sim_swap( origin_netuid=origin_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=hotkey_ss58, destination_netuid=destination_netuid, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, amount=amount_to_swap.rao, ), subtensor.get_extrinsic_fee(call, wallet.coldkeypub), @@ -903,7 +895,9 @@ async def swap_stake( origin_hotkey=hotkey_ss58, destination_hotkey=hotkey_ss58, amount_to_move=amount_to_swap, - stake_fee=stake_fee, + stake_fee=sim_swap.alpha_fee + if origin_netuid != 0 + else sim_swap.tao_fee, extrinsic_fee=extrinsic_fee, ) except ValueError: diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index 3a37b8cbe..ecec77fa5 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -200,16 +200,6 @@ async def unstake( ) continue # Skip to the next subnet - useful when single amount is specified for all subnets - stake_fee = await subtensor.get_stake_fee( - origin_hotkey_ss58=staking_address_ss58, - origin_netuid=netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=None, - destination_netuid=None, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - amount=amount_to_unstake_as_balance.rao, - ) - try: current_price = subnet_info.price.tao if safe_staking: @@ -240,10 +230,10 @@ async def unstake( netuid=netuid, amount=amount_to_unstake_as_balance, ) - rate = current_price - received_amount = ( - (amount_to_unstake_as_balance - stake_fee) * rate - ) - extrinsic_fee + sim_swap = await subtensor.sim_swap( + netuid, 0, amount_to_unstake_as_balance.rao + ) + received_amount = sim_swap.tao_amount - extrinsic_fee except ValueError: continue total_received_amount += received_amount @@ -266,7 +256,7 @@ async def unstake( str(amount_to_unstake_as_balance), # Amount to Unstake f"{subnet_info.price.tao:.6f}" + f"(τ/{Balance.get_unit(netuid)})", # Rate - str(stake_fee.set_unit(netuid)), # Fee + str(sim_swap.alpha_fee), # Fee str(extrinsic_fee), # Extrinsic fee str(received_amount), # Received Amount # slippage_pct, # Slippage Percent @@ -494,15 +484,6 @@ async def unstake_all( hotkey_display = hotkey_names.get(stake.hotkey_ss58, stake.hotkey_ss58) subnet_info = all_sn_dynamic_info.get(stake.netuid) stake_amount = stake.stake - stake_fee = await subtensor.get_stake_fee( - origin_hotkey_ss58=stake.hotkey_ss58, - origin_netuid=stake.netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_hotkey_ss58=None, - destination_netuid=None, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - amount=stake_amount.rao, - ) try: current_price = subnet_info.price.tao @@ -515,8 +496,8 @@ async def unstake_all( subtensor, hotkey_ss58=stake.hotkey_ss58, ) - rate = current_price - received_amount = ((stake_amount - stake_fee) * rate) - extrinsic_fee + sim_swap = await subtensor.sim_swap(stake.netuid, 0, stake_amount.rao) + received_amount = sim_swap.tao_amount - extrinsic_fee if received_amount < Balance.from_tao(0): print_error("Not enough Alpha to pay the transaction fee.") @@ -532,7 +513,7 @@ async def unstake_all( str(stake_amount), f"{float(subnet_info.price):.6f}" + f"({Balance.get_unit(0)}/{Balance.get_unit(stake.netuid)})", - str(stake_fee), + str(sim_swap.alpha_fee), str(extrinsic_fee), str(received_amount), ) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 7b90641fa..1112d195d 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -823,6 +823,7 @@ async def wallet_list(wallet_path: str, json_output: bool): for wallet in wallets: if ( wallet.coldkeypub_file.exists_on_device() + and os.path.isfile(wallet.coldkeypub_file.path) and not wallet.coldkeypub_file.is_encrypted() ): coldkeypub_str = wallet.coldkeypub.ss58_address @@ -852,6 +853,9 @@ async def wallet_list(wallet_path: str, json_output: bool): except KeyFileError: hkey_ss58 = hkey.get_hotkeypub().ss58_address pub_only = True + except AttributeError: + hkey_ss58 = hkey.hotkey.ss58_address + pub_only = False try: data = ( f"[bold red]Hotkey[/bold red] [green]{hkey.hotkey_str}[/green] " diff --git a/pyproject.toml b/pyproject.toml index 651cdaf9c..56814fe27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor-cli" -version = "9.11.0" +version = "9.11.1" description = "Bittensor CLI" readme = "README.md" authors = [ diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 1b93ac0ae..0e1b13cc6 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -1,4 +1,5 @@ import asyncio +from collections.abc import Iterator, Callable import logging import os import re @@ -8,11 +9,13 @@ import subprocess import sys import time +from typing import Generator +import bittensor_wallet.keypair import pytest from async_substrate_interface.async_substrate import AsyncSubstrateInterface -from .utils import setup_wallet +from .utils import setup_wallet, ExecCommand LOCALNET_IMAGE_NAME = "ghcr.io/opentensor/subtensor-localnet:devnet-ready" @@ -31,7 +34,7 @@ def wait_for_node_start(process, pattern, timestamp: int = None): # Fixture for setting up and tearing down a localnet.sh chain between tests @pytest.fixture(scope="function") -def local_chain(request): +def local_chain(request) -> Iterator[AsyncSubstrateInterface]: """Determines whether to run the localnet.sh script in a subprocess or a Docker container.""" args = request.param if hasattr(request, "param") else None params = "" if args is None else f"{args}" @@ -58,7 +61,7 @@ def local_chain(request): yield from legacy_runner(request) -def legacy_runner(request): +def legacy_runner(request) -> Iterator[AsyncSubstrateInterface]: param = request.param if hasattr(request, "param") else None # Get the environment variable for the script path script_path = os.getenv("LOCALNET_SH_PATH") @@ -103,7 +106,7 @@ def legacy_runner(request): process.wait() -def docker_runner(params): +def docker_runner(params) -> Iterator[AsyncSubstrateInterface]: """Starts a Docker container before tests and gracefully terminates it after.""" def is_docker_running(): @@ -211,7 +214,19 @@ def try_start_docker(): @pytest.fixture(scope="function") -def wallet_setup(): +def wallet_setup() -> Generator[ + Callable[ + [str], + tuple[ + bittensor_wallet.Keypair, + bittensor_wallet.Wallet, + str, + ExecCommand, + ], + ], + None, + None, +]: wallet_paths = [] def _setup_wallet(uri: str): diff --git a/tests/e2e_tests/test_hyperparams_setting.py b/tests/e2e_tests/test_hyperparams_setting.py index 03d8616c3..3af86c140 100644 --- a/tests/e2e_tests/test_hyperparams_setting.py +++ b/tests/e2e_tests/test_hyperparams_setting.py @@ -1,6 +1,8 @@ +import asyncio import json from bittensor_cli.src import HYPERPARAMS +from .utils import turn_off_hyperparam_freeze_window """ Verify commands: @@ -18,7 +20,12 @@ def test_hyperparams_setting(local_chain, wallet_setup): keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( wallet_path_alice ) - + try: + asyncio.run(turn_off_hyperparam_freeze_window(local_chain, wallet_alice)) + except ValueError: + print( + "Skipping turning off hyperparams freeze window. This indicates the call does not exist on the chain you are testing." + ) # Register a subnet with sudo as Alice result = exec_command_alice( command="subnets", diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index c8a7b7d4c..218ef91f0 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -1,7 +1,9 @@ +import asyncio import json import re from bittensor_cli.src.bittensor.balances import Balance +from .utils import turn_off_hyperparam_freeze_window """ Verify commands: @@ -40,6 +42,12 @@ def liquidity_list(): keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( wallet_path_alice ) + try: + asyncio.run(turn_off_hyperparam_freeze_window(local_chain, wallet_alice)) + except ValueError: + print( + "Skipping turning off hyperparams freeze window. This indicates the call does not exist on the chain you are testing." + ) # Register a subnet with sudo as Alice result = exec_command_alice( diff --git a/tests/e2e_tests/test_staking_sudo.py b/tests/e2e_tests/test_staking_sudo.py index 359fbb508..9034c51da 100644 --- a/tests/e2e_tests/test_staking_sudo.py +++ b/tests/e2e_tests/test_staking_sudo.py @@ -1,7 +1,9 @@ +import asyncio import json import re from bittensor_cli.src.bittensor.balances import Balance +from .utils import turn_off_hyperparam_freeze_window """ Verify commands: @@ -45,6 +47,12 @@ def test_staking(local_chain, wallet_setup): keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( wallet_path_alice ) + try: + asyncio.run(turn_off_hyperparam_freeze_window(local_chain, wallet_alice)) + except ValueError: + print( + "Skipping turning off hyperparams freeze window. This indicates the call does not exist on the chain you are testing." + ) burn_cost = exec_command_alice( "subnets", diff --git a/tests/e2e_tests/test_unstaking.py b/tests/e2e_tests/test_unstaking.py index 68af71087..4b7ca0765 100644 --- a/tests/e2e_tests/test_unstaking.py +++ b/tests/e2e_tests/test_unstaking.py @@ -4,7 +4,7 @@ from bittensor_cli.src.bittensor.balances import Balance -from btcli.tests.e2e_tests.utils import set_storage_extrinsic +from .utils import set_storage_extrinsic def test_unstaking(local_chain, wallet_setup): diff --git a/tests/e2e_tests/utils.py b/tests/e2e_tests/utils.py index b8b729b3e..9917c2c10 100644 --- a/tests/e2e_tests/utils.py +++ b/tests/e2e_tests/utils.py @@ -5,9 +5,10 @@ import shutil import subprocess import sys -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Protocol from bittensor_wallet import Keypair, Wallet +from click.testing import Result from packaging.version import parse as parse_version, Version from typer.testing import CliRunner @@ -20,7 +21,19 @@ templates_repo = "templates repository" -def setup_wallet(uri: str): +class ExecCommand(Protocol): + """Type Protocol for setup_wallet's exec_command fn""" + + def __call__( + self, + command: str, + sub_command: str, + extra_args: Optional[list[str]], + inputs: Optional[list[str]], + ) -> Result: ... + + +def setup_wallet(uri: str) -> tuple[Keypair, Wallet, str, ExecCommand]: keypair = Keypair.create_from_uri(uri) wallet_path = f"/tmp/btcli-e2e-wallet-{uri.strip('/')}" wallet = Wallet(path=wallet_path) @@ -32,7 +45,7 @@ def exec_command( command: str, sub_command: str, extra_args: Optional[list[str]] = None, - inputs: list[str] = None, + inputs: Optional[list[str]] = None, ): extra_args = extra_args or [] cli_manager = CLIManager() @@ -370,3 +383,29 @@ async def set_storage_extrinsic( print(":white_heavy_check_mark: [dark_sea_green_3]Success[/dark_sea_green_3]") return response + + +async def turn_off_hyperparam_freeze_window( + substrate: "AsyncSubstrateInterface", wallet: Wallet +): + call = await substrate.compose_call( + call_module="Sudo", + call_function="sudo", + call_params={ + "call": await substrate.compose_call( + call_module="AdminUtils", + call_function="sudo_set_admin_freeze_window", + call_params={"window": 0}, + ) + }, + ) + extrinsic = await substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = await substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + return await response.is_success, await response.error_message