diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 2c6dd8bd1b..f714bdb361 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -76,7 +76,7 @@ u16_normalized_float, _decode_hex_identity_dict, ) -from bittensor.utils.balance import Balance, fixed_to_float, FixedPoint +from bittensor.utils.balance import Balance, fixed_to_float from bittensor.utils.btlogging import logging from bittensor.utils.delegates_details import DelegatesDetails from bittensor.utils.weight_utils import generate_weight_hash @@ -822,6 +822,8 @@ async def get_balance( ) return Balance(balance["data"]["free"]) + balance = get_balance + async def get_balances( self, *addresses: str, @@ -1501,7 +1503,7 @@ async def get_stake( block_hash = await self.determine_block_hash(block, block_hash, reuse_block) # Get alpha shares - alpha_shares: FixedPoint = await self.query_module( + alpha_shares = await self.query_module( module="SubtensorModule", name="Alpha", block_hash=block_hash, @@ -1520,7 +1522,7 @@ async def get_stake( hotkey_alpha: int = getattr(hotkey_alpha_result, "value", 0) # Get total hotkey shares - hotkey_shares: FixedPoint = await self.query_module( + hotkey_shares = await self.query_module( module="SubtensorModule", name="TotalHotkeyShares", block_hash=block_hash, @@ -1541,7 +1543,11 @@ async def get_stake( get_stake_for_coldkey_and_hotkey = get_stake async def get_stake_for_coldkey( - self, coldkey_ss58: str, block: Optional[int] = None + self, + coldkey_ss58: str, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Optional[list["StakeInfo"]]: """ Retrieves the stake information for a given coldkey. @@ -1549,12 +1555,16 @@ async def get_stake_for_coldkey( Args: coldkey_ss58 (str): The SS58 address of the coldkey. block (Optional[int]): The block number at which to query the stake information. + block_hash (Optional[str]): The hash of the blockchain block number for the query. + reuse_block (bool): Whether to reuse the last-used block hash. Returns: Optional[list[StakeInfo]]: A list of StakeInfo objects, or ``None`` if no stake information is found. """ encoded_coldkey = ss58_to_vec_u8(coldkey_ss58) - block_hash = await self.determine_block_hash(block) + block_hash = await self.determine_block_hash( + block=block, block_hash=block_hash, reuse_block=reuse_block + ) hex_bytes_result = await self.query_runtime_api( runtime_api="StakeInfoRuntimeApi", @@ -3455,7 +3465,7 @@ async def transfer( self, wallet: "Wallet", dest: str, - amount: Union["Balance", float], + amount: "Balance", transfer_all: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -3506,6 +3516,7 @@ async def unstake( wallet (bittensor_wallet.wallet): The wallet associated with the neuron from which the stake is being removed. hotkey_ss58 (Optional[str]): The ``SS58`` address of the hotkey account to unstake from. + netuid (Optional[int]): Subnet unique ID. amount (Balance): The amount of TAO to unstake. If not specified, unstakes all. wait_for_inclusion (bool): Waits for the transaction to be included in a block. wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. @@ -3543,6 +3554,7 @@ async def unstake_multiple( wallet (bittensor_wallet.Wallet): The wallet linked to the coldkey from which the stakes are being withdrawn. hotkey_ss58s (List[str]): A list of hotkey ``SS58`` addresses to unstake from. + netuids (list[int]): Subnets unique IDs. amounts (List[Union[Balance, float]]): The amounts of TAO to unstake from each hotkey. If not provided, unstakes all available stakes. wait_for_inclusion (bool): Waits for the transaction to be included in a block. diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 1af2f8c399..4c3272d3c9 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -5,11 +5,11 @@ if TYPE_CHECKING: from bittensor_wallet import Wallet - from bittensor.core.subtensor import Subtensor + from bittensor.core.async_subtensor import AsyncSubtensor async def transfer_stake_extrinsic( - subtensor: "Subtensor", + subtensor: "AsyncSubtensor", wallet: "Wallet", destination_coldkey_ss58: str, hotkey_ss58: str, @@ -19,6 +19,24 @@ async def transfer_stake_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: + """ + Transfers stake from one coldkey to another in the Bittensor network. + + Args: + subtensor (AsyncSubtensor): The subtensor instance to interact with the blockchain. + wallet (Wallet): The wallet containing the coldkey to authorize the transfer. + destination_coldkey_ss58 (str): SS58 address of the destination coldkey. + hotkey_ss58 (str): SS58 address of the hotkey associated with the stake. + origin_netuid (int): Network UID of the origin subnet. + destination_netuid (int): Network UID of the destination subnet. + amount (Balance): The amount of stake to transfer as a `Balance` object. + wait_for_inclusion (bool): If True, waits for transaction inclusion in a block. Defaults to `True`. + wait_for_finalization (bool): If True, waits for transaction finalization. Defaults to `False`. + + Returns: + bool: True if the transfer was successful, False otherwise. + """ + amount.set_unit(netuid=origin_netuid) # Verify ownership hotkey_owner = await subtensor.get_hotkey_owner(hotkey_ss58) @@ -107,7 +125,7 @@ async def transfer_stake_extrinsic( async def swap_stake_extrinsic( - subtensor: "Subtensor", + subtensor: "AsyncSubtensor", wallet: "Wallet", hotkey_ss58: str, origin_netuid: int, @@ -116,6 +134,22 @@ async def swap_stake_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: + """ + Swaps stake from one subnet to another for a given hotkey in the Bittensor network. + + Args: + subtensor (AsyncSubtensor): The subtensor instance to interact with the blockchain. + wallet (Wallet): The wallet containing the coldkey to authorize the swap. + hotkey_ss58 (str): SS58 address of the hotkey associated with the stake. + origin_netuid (int): Network UID of the origin subnet. + destination_netuid (int): Network UID of the destination subnet. + amount (Balance): The amount of stake to swap as a `Balance` object. + wait_for_inclusion (bool): If True, waits for transaction inclusion in a block. Defaults to True. + wait_for_finalization (bool): If True, waits for transaction finalization. Defaults to False. + + Returns: + bool: True if the swap was successful, False otherwise. + """ amount.set_unit(netuid=origin_netuid) # Verify ownership hotkey_owner = await subtensor.get_hotkey_owner(hotkey_ss58) @@ -203,7 +237,7 @@ async def swap_stake_extrinsic( async def move_stake_extrinsic( - subtensor: "Subtensor", + subtensor: "AsyncSubtensor", wallet: "Wallet", origin_hotkey: str, origin_netuid: int, @@ -213,6 +247,23 @@ async def move_stake_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: + """ + Moves stake from one hotkey to another within subnets in the Bittensor network. + + Args: + subtensor (Subtensor): The subtensor instance to interact with the blockchain. + wallet (Wallet): The wallet containing the coldkey to authorize the move. + origin_hotkey (str): SS58 address of the origin hotkey associated with the stake. + origin_netuid (int): Network UID of the origin subnet. + destination_hotkey (str): SS58 address of the destination hotkey. + destination_netuid (int): Network UID of the destination subnet. + amount (Balance): The amount of stake to move as a `Balance` object. + wait_for_inclusion (bool): If True, waits for transaction inclusion in a block. Defaults to True. + wait_for_finalization (bool): If True, waits for transaction finalization. Defaults to False. + + Returns: + bool: True if the move was successful, False otherwise. + """ amount.set_unit(netuid=origin_netuid) # Verify ownership of origin hotkey origin_owner = await subtensor.get_hotkey_owner(origin_hotkey) diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 5c6d9cbe77..00bc243cc9 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -5,6 +5,7 @@ from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging +from bittensor.core.extrinsics.utils import get_old_stakes if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -184,27 +185,6 @@ async def add_stake_multiple_extrinsic( success: `True` if extrinsic was finalized or included in the block. `True` if any wallet was staked. If we did not wait for finalization/inclusion, the response is `True`. """ - - async def get_old_stakes() -> list[Balance]: - old_stakes = [] - all_stakes = await subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - for hotkey_ss58, netuid in zip(hotkey_ss58s, netuids): - stake = next( - ( - stake.stake - for stake in all_stakes - if stake.hotkey_ss58 == hotkey_ss58 - and stake.coldkey_ss58 == wallet.coldkeypub.ss58_address - and stake.netuid == netuid - ), - Balance.from_tao(0), # Default to 0 balance if no match found - ) - old_stakes.append(stake) - - return old_stakes - if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s ): @@ -239,7 +219,13 @@ async def get_old_stakes() -> list[Balance]: f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" ) block_hash = await subtensor.substrate.get_chain_head() - old_stakes: list[Balance] = await get_old_stakes() + + all_stakes = await subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash + ) + old_stakes: list[Balance] = get_old_stakes( + wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes + ) # Remove existential balance to keep key alive. # Keys must maintain a balance of at least 1000 rao to stay alive. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index f56e3706eb..352f8441eb 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -5,6 +5,7 @@ from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging +from bittensor.core.extrinsics.utils import get_old_stakes if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -164,27 +165,6 @@ async def unstake_multiple_extrinsic( success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. Flag is ``True`` if any wallet was unstaked. If we did not wait for finalization / inclusion, the response is ``True``. """ - - async def get_old_stakes() -> list[Balance]: - old_stakes = [] - all_stakes = await subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - for hotkey_ss58, netuid in zip(hotkey_ss58s, netuids): - stake = next( - ( - stake.stake - for stake in all_stakes - if stake.hotkey_ss58 == hotkey_ss58 - and stake.coldkey_ss58 == wallet.coldkeypub.ss58_address - and stake.netuid == netuid - ), - Balance.from_tao(0), # Default to 0 balance if no match found - ) - old_stakes.append(stake) - - return old_stakes - if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s ): @@ -223,10 +203,18 @@ async def get_old_stakes() -> list[Balance]: logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" ) + block_hash = await subtensor.substrate.get_chain_head() - old_balance, old_stakes = await asyncio.gather( + + all_stakes, old_balance = await asyncio.gather( + subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash + ), subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash), - get_old_stakes(), + ) + + old_stakes: list[Balance] = get_old_stakes( + wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes ) successful_unstakes = 0 diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 13ead8fd41..b6b0793a2e 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -2,6 +2,7 @@ from typing import Optional, TYPE_CHECKING, Sequence from bittensor.core.errors import StakeError, NotRegisteredError +from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -27,6 +28,7 @@ def add_stake_extrinsic( subtensor: the Subtensor object to use wallet: Bittensor wallet object. hotkey_ss58: The `ss58` address of the hotkey account to stake to defaults to the wallet's hotkey. + netuid (Optional[int]): Subnet unique ID. amount: Amount to stake as Bittensor balance, `None` if staking all. wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns `False` if the extrinsic fails to enter the block within the timeout. @@ -174,26 +176,6 @@ def add_stake_multiple_extrinsic( not wait for finalization/inclusion, the response is `True`. """ - def get_old_stakes() -> list[Balance]: - old_stakes = [] - all_stakes = subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - for hotkey_ss58, netuid in zip(hotkey_ss58s, netuids): - stake = next( - ( - stake.stake - for stake in all_stakes - if stake.hotkey_ss58 == hotkey_ss58 - and stake.coldkey_ss58 == wallet.coldkeypub.ss58_address - and stake.netuid == netuid - ), - Balance.from_tao(0), # Default to 0 balance if no match found - ) - old_stakes.append(stake) - - return old_stakes - if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s ): @@ -209,6 +191,7 @@ def get_old_stakes() -> list[Balance]: raise ValueError("netuids must be a list of the same length as hotkey_ss58s") new_amounts: Sequence[Optional[Balance]] + if amounts is None: new_amounts = [None] * len(hotkey_ss58s) else: @@ -228,7 +211,12 @@ def get_old_stakes() -> list[Balance]: f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" ) block = subtensor.get_current_block() - old_stakes: list[Balance] = get_old_stakes() + all_stakes = subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, + ) + old_stakes: list[Balance] = get_old_stakes( + wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes + ) # Remove existential balance to keep key alive. # Keys must maintain a balance of at least 1000 rao to stay alive. diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 0279b7079c..9a9b5dd908 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -2,6 +2,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.errors import StakeError, NotRegisteredError +from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.utils import unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -27,7 +28,8 @@ def unstake_extrinsic( wallet (bittensor_wallet.Wallet): Bittensor wallet object. hotkey_ss58 (Optional[str]): The ``ss58`` address of the hotkey to unstake from. By default, the wallet hotkey is used. - amount (Union[Balance, float]): Amount to stake as Bittensor balance, or ``float`` interpreted as Tao. + netuid (Optional[int]): Subnet unique id. + amount (Union[Balance]): Amount to stake as Bittensor balance. wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or returns ``False`` if the extrinsic fails to enter the block within the timeout. wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning @@ -147,6 +149,7 @@ def unstake_multiple_extrinsic( subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance. wallet (bittensor_wallet.Wallet): The wallet with the coldkey to unstake to. hotkey_ss58s (List[str]): List of hotkeys to unstake from. + netuids (List[int]): List of subnets unique IDs to unstake from. amounts (List[Balance]): List of amounts to unstake. If ``None``, unstake all. wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or returns ``False`` if the extrinsic fails to enter the block within the timeout. @@ -158,26 +161,6 @@ def unstake_multiple_extrinsic( wallet was unstaked. If we did not wait for finalization / inclusion, the response is ``True``. """ - def get_old_stakes() -> list[Balance]: - old_stakes = [] - all_stakes = subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - for hotkey_ss58, netuid in zip(hotkey_ss58s, netuids): - stake = next( - ( - stake.stake - for stake in all_stakes - if stake.hotkey_ss58 == hotkey_ss58 - and stake.coldkey_ss58 == wallet.coldkeypub.ss58_address - and stake.netuid == netuid - ), - Balance.from_tao(0), # Default to 0 balance if no match found - ) - old_stakes.append(stake) - - return old_stakes - if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s ): @@ -216,7 +199,12 @@ def get_old_stakes() -> list[Balance]: ) block = subtensor.get_current_block() old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - old_stakes = get_old_stakes() + all_stakes = subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address + ) + old_stakes = get_old_stakes( + wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes + ) successful_unstakes = 0 for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index b9ec867ad1..5e2990c392 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -5,15 +5,18 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.utils import format_error_message +from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging if TYPE_CHECKING: - from bittensor.core.subtensor import Subtensor + from bittensor_wallet import Wallet from bittensor.core.async_subtensor import AsyncSubtensor from async_substrate_interface import ( AsyncExtrinsicReceipt, ExtrinsicReceipt, ) + from bittensor.core.subtensor import Subtensor + from bittensor.core.chain_data import StakeInfo from scalecodec.types import GenericExtrinsic @@ -91,3 +94,42 @@ async def async_submit_extrinsic( # Re-raise the exception for retrying of the extrinsic call. If we remove the retry logic, # the raise will need to be removed. raise + + +def get_old_stakes( + wallet: "Wallet", + hotkey_ss58s: list[str], + netuids: list[int], + all_stakes: list["StakeInfo"], +) -> list[Balance]: + """ + Retrieve the previous staking balances for a wallet's hotkeys across given netuids. + + This function searches through the provided staking data to find the stake amounts + for the specified hotkeys and netuids associated with the wallet's coldkey. If no match + is found for a particular hotkey and netuid combination, a default balance of zero is returned. + + Args: + wallet (Wallet): The wallet containing the coldkey to compare with stake data. + hotkey_ss58s (list[str]): List of hotkey SS58 addresses for which stakes are retrieved. + netuids (list[int]): List of network unique identifiers (netuids) corresponding to the hotkeys. + all_stakes (list[StakeInfo]): A collection of all staking information to search through. + + Returns: + list[Balance]: A list of Balances, each representing the stake for a given hotkey and netuid. + """ + old_stakes = [] + for hotkey_ss58, netuid in zip(hotkey_ss58s, netuids): + stake = next( + ( + stake.stake + for stake in all_stakes + if stake.hotkey_ss58 == hotkey_ss58 + and stake.coldkey_ss58 == wallet.coldkeypub.ss58_address + and stake.netuid == netuid + ), + Balance.from_tao(0), # Default to 0 balance if no match found + ) + old_stakes.append(stake) + + return old_stakes diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 003943ae65..bc58b65e3e 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -38,7 +38,6 @@ "n", "block", "stake", - "total_stake", "ranks", "trust", "consensus", @@ -1011,6 +1010,11 @@ def __init__( self.neurons = [] self.subtensor = subtensor self.should_sync = sync + self.alpha_stake: list["Balance"] = [] + self.tao_stake: list["Balance"] = [] + self.stake: list["Balance"] = [] + self.axons: list["AxonInfo"] = [] + self.total_stake: list["Balance"] = [] def load_from_path(self, dir_path: str) -> "AsyncMetagraph": """ @@ -1136,6 +1140,11 @@ def __init__( self.neurons = [] self.subtensor = subtensor self.should_sync = sync + self.alpha_stake: list["Balance"] = [] + self.tao_stake: list["Balance"] = [] + self.stake: list["Balance"] = [] + self.axons: list["AxonInfo"] = [] + self.total_stake: list["Balance"] = [] def load_from_path(self, dir_path: str) -> "AsyncMetagraph": """ @@ -1179,7 +1188,6 @@ def load_from_path(self, dir_path: str) -> "AsyncMetagraph": self.block = state_dict["block"] self.uids = state_dict["uids"] self.stake = state_dict["stake"] - self.total_stake = state_dict["total_stake"] self.ranks = state_dict["ranks"] self.trust = state_dict["trust"] self.consensus = state_dict["consensus"] @@ -1770,7 +1778,7 @@ def _get_all_stakes_from_chain(self, subtensor: Optional["Subtensor"] = None): """Fills in the stake associated attributes of a class instance from a chain response.""" try: if not subtensor: - subtensor = self._initialize_subtensor() + subtensor = self._initialize_subtensor(subtensor=subtensor) hex_bytes_result = subtensor.query_runtime_api( runtime_api="SubnetInfoRuntimeApi", diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 1263afd98d..f799a582e4 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -236,7 +236,7 @@ def query_module( name: str, block: Optional[int] = None, params: Optional[list] = None, - ) -> "ScaleType": + ) -> Union["ScaleType", "FixedPoint"]: """ Queries any module storage on the Bittensor blockchain with the specified parameters and block number. This function is a generic query interface that allows for flexible and diverse data retrieval from various @@ -1109,7 +1109,7 @@ def get_neuron_certificate( This function is used for certificate discovery for setting up mutual tls communication between neurons. """ - certificate = self.query_module( + certificate: ScaleType = self.query_module( module="SubtensorModule", name="NeuronCertificates", block=block, @@ -1190,12 +1190,14 @@ def get_stake( block=block, params=[hotkey_ss58, coldkey_ss58, netuid], ) - hotkey_alpha: int = self.query_module( + hotkey_alpha_obj: ScaleType = self.query_module( module="SubtensorModule", name="TotalHotkeyAlpha", block=block, params=[hotkey_ss58, netuid], - ).value + ) + hotkey_alpha = hotkey_alpha_obj.value + hotkey_shares: FixedPoint = self.query_module( module="SubtensorModule", name="TotalHotkeyShares", diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index b3301c0096..8ed504429b 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -1,5 +1,7 @@ from typing import Union, Optional, TypedDict +from scalecodec import ScaleType + from bittensor.core import settings @@ -282,10 +284,9 @@ class FixedPoint(TypedDict): bits: int -def fixed_to_float(fixed: FixedPoint) -> float: +def fixed_to_float(fixed: Union[FixedPoint, ScaleType]) -> float: # Currently this is stored as a U64F64 # which is 64 bits of integer and 64 bits of fractional - uint_bits = 64 frac_bits = 64 data: int = fixed["bits"] diff --git a/requirements/prod.txt b/requirements/prod.txt index 55777aca30..0958a2b227 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -25,4 +25,4 @@ uvicorn websockets>=14.1 bittensor-commit-reveal>=0.2.0 bittensor-wallet>=3.0.0 -async-substrate-interface==1.0.0rc5 +async-substrate-interface==1.0.0rc6 diff --git a/tests/integration_tests/test_metagraph_integration.py b/tests/integration_tests/test_metagraph_integration.py index 3344ab6ae4..bd52b4e845 100644 --- a/tests/integration_tests/test_metagraph_integration.py +++ b/tests/integration_tests/test_metagraph_integration.py @@ -64,7 +64,6 @@ def test_state_dict(self): assert "n" in state assert "block" in state assert "stake" in state - assert "total_stake" in state assert "ranks" in state assert "trust" in state assert "consensus" in state diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index b6fc9cb38f..6d9522148d 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -19,7 +19,8 @@ def test_add_stake_extrinsic(mocker): } ) hotkey_ss58 = "hotkey" - amount = 1.1 + fake_netuid = 1 + amount = Balance.from_tao(1.1) wait_for_inclusion = True wait_for_finalization = True @@ -28,6 +29,7 @@ def test_add_stake_extrinsic(mocker): subtensor=fake_subtensor, wallet=fake_wallet, hotkey_ss58=hotkey_ss58, + netuid=fake_netuid, amount=amount, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, @@ -39,10 +41,7 @@ def test_add_stake_extrinsic(mocker): fake_subtensor.substrate.compose_call.assert_called_once_with( call_module="SubtensorModule", call_function="add_stake", - call_params={ - "hotkey": "hotkey", - "amount_staked": 9, - }, + call_params={"hotkey": "hotkey", "amount_staked": 9, "netuid": 1}, ) fake_subtensor.sign_and_send_extrinsic.assert_called_once_with( fake_subtensor.substrate.compose_call.return_value, @@ -80,13 +79,17 @@ def test_add_stake_multiple_extrinsic(mocker): "substrate.query.return_value": 0, } ) + mocker.patch.object( + staking, "get_old_stakes", return_value=[Balance(1.1), Balance(0.3)] + ) fake_wallet = mocker.Mock( **{ "coldkeypub.ss58_address": "hotkey_owner", } ) hotkey_ss58s = ["hotkey1", "hotkey2"] - amounts = [1.1, 2.2] + netuids = [1, 2] + amounts = [Balance.from_tao(1.1), Balance.from_tao(2.2)] wait_for_inclusion = True wait_for_finalization = True @@ -95,6 +98,7 @@ def test_add_stake_multiple_extrinsic(mocker): subtensor=fake_subtensor, wallet=fake_wallet, hotkey_ss58s=hotkey_ss58s, + netuids=netuids, amounts=amounts, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, @@ -109,8 +113,9 @@ def test_add_stake_multiple_extrinsic(mocker): call_module="SubtensorModule", call_function="add_stake", call_params={ - "hotkey": "hotkey1", - "amount_staked": 1099999666, + "hotkey": "hotkey2", + "amount_staked": 2199999333, + "netuid": 2, }, ) fake_subtensor.substrate.compose_call.assert_any_call( @@ -119,6 +124,7 @@ def test_add_stake_multiple_extrinsic(mocker): call_params={ "hotkey": "hotkey2", "amount_staked": 2199999333, + "netuid": 2, }, ) fake_subtensor.sign_and_send_extrinsic.assert_called_with( diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index e8c84c3612..9d0ed8ec03 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -9,12 +9,14 @@ def test_unstake_extrinsic(mocker): "get_hotkey_owner.return_value": "hotkey_owner", "get_stake_for_coldkey_and_hotkey.return_value": Balance(10.0), "sign_and_send_extrinsic.return_value": (True, ""), + "get_stake.return_value": Balance(10.0), } ) fake_wallet = mocker.Mock() fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58 = "hotkey" - amount = 1.1 + fake_netuid = 1 + amount = Balance.from_tao(1.1) wait_for_inclusion = True wait_for_finalization = True @@ -23,6 +25,7 @@ def test_unstake_extrinsic(mocker): subtensor=fake_subtensor, wallet=fake_wallet, hotkey_ss58=hotkey_ss58, + netuid=fake_netuid, amount=amount, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, @@ -37,6 +40,7 @@ def test_unstake_extrinsic(mocker): call_params={ "hotkey": "hotkey", "amount_unstaked": 1100000000, + "netuid": 1, }, ) fake_subtensor.sign_and_send_extrinsic.assert_called_once_with( @@ -53,15 +57,19 @@ def test_unstake_multiple_extrinsic(mocker): fake_subtensor = mocker.Mock( **{ "get_hotkey_owner.return_value": "hotkey_owner", - "get_stake_for_coldkey_and_hotkey.return_value": Balance(10.0), + "get_stake_for_coldkey_and_hotkey.return_value": [Balance(10.0)], "sign_and_send_extrinsic.return_value": (True, ""), "tx_rate_limit.return_value": 0, } ) + mocker.patch.object( + unstaking, "get_old_stakes", return_value=[Balance(1.1), Balance(0.3)] + ) fake_wallet = mocker.Mock() fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58s = ["hotkey1", "hotkey2"] - amounts = [1.1, 1.2] + fake_netuids = [1, 2] + amounts = [Balance.from_tao(1.1), Balance.from_tao(1.2)] wait_for_inclusion = True wait_for_finalization = True @@ -70,6 +78,7 @@ def test_unstake_multiple_extrinsic(mocker): subtensor=fake_subtensor, wallet=fake_wallet, hotkey_ss58s=hotkey_ss58s, + netuids=fake_netuids, amounts=amounts, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, @@ -77,8 +86,8 @@ def test_unstake_multiple_extrinsic(mocker): # Asserts assert result is True - assert fake_subtensor.substrate.compose_call.call_count == 2 - assert fake_subtensor.sign_and_send_extrinsic.call_count == 2 + assert fake_subtensor.substrate.compose_call.call_count == 1 + assert fake_subtensor.sign_and_send_extrinsic.call_count == 1 fake_subtensor.substrate.compose_call.assert_any_call( call_module="SubtensorModule", @@ -86,14 +95,16 @@ def test_unstake_multiple_extrinsic(mocker): call_params={ "hotkey": "hotkey1", "amount_unstaked": 1100000000, + "netuid": 1, }, ) fake_subtensor.substrate.compose_call.assert_any_call( call_module="SubtensorModule", call_function="remove_stake", call_params={ - "hotkey": "hotkey2", - "amount_unstaked": 1200000000, + "hotkey": "hotkey1", + "amount_unstaked": 1100000000, + "netuid": 1, }, ) fake_subtensor.sign_and_send_extrinsic.assert_called_with( diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index caa80c9f01..8d4eda3339 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2,8 +2,9 @@ from bittensor_wallet import Wallet from bittensor.core import async_subtensor -from bittensor.core.chain_data import proposal_vote_data from bittensor.core.async_subtensor import AsyncSubtensor +from bittensor.core.chain_data import proposal_vote_data +from bittensor.utils.balance import Balance @pytest.fixture(autouse=True) @@ -489,17 +490,17 @@ async def test_get_stake_for_coldkey_and_hotkey(subtensor, mocker): ) # Asserts - mocked_substrate_query.assert_called_once_with( + mocked_substrate_query.assert_awaited_with( module="SubtensorModule", - storage_function="Stake", - params=["hotkey", "coldkey"], + storage_function="TotalHotkeyShares", + params=["hotkey", None], block_hash=None, reuse_block_hash=False, ) - assert result == spy_balance.from_rao.return_value - spy_balance.from_rao.assert_called_once_with( - mocked_substrate_query.return_value.value - ) + assert mocked_substrate_query.call_count == 3 + assert result == spy_balance.from_rao.return_value.set_unit.return_value + spy_balance.from_rao.assert_called() + assert spy_balance.from_rao.call_count == 1 @pytest.mark.asyncio @@ -2478,7 +2479,7 @@ async def test_transfer_success(subtensor, mocker): # Preps fake_wallet = mocker.Mock() fake_destination = "destination_address" - fake_amount = 100.0 + fake_amount = Balance.from_tao(100.0) fake_transfer_all = False mocked_transfer_extrinsic = mocker.AsyncMock(return_value=True) @@ -2486,11 +2487,6 @@ async def test_transfer_success(subtensor, mocker): async_subtensor, "transfer_extrinsic", mocked_transfer_extrinsic ) - mocked_balance_from_tao = mocker.Mock() - mocker.patch.object( - async_subtensor.Balance, "from_tao", return_value=mocked_balance_from_tao - ) - # Call result = await subtensor.transfer( wallet=fake_wallet, @@ -2504,7 +2500,7 @@ async def test_transfer_success(subtensor, mocker): subtensor=subtensor, wallet=fake_wallet, dest=fake_destination, - amount=mocked_balance_from_tao, + amount=fake_amount, transfer_all=fake_transfer_all, wait_for_inclusion=True, wait_for_finalization=False, diff --git a/tests/unit_tests/test_metagraph.py b/tests/unit_tests/test_metagraph.py index 1c8efe4fcc..52da2c366f 100644 --- a/tests/unit_tests/test_metagraph.py +++ b/tests/unit_tests/test_metagraph.py @@ -132,6 +132,7 @@ def metagraph_instance(mocker): metagraph._assign_neurons = mocker.AsyncMock() metagraph._set_metagraph_attributes = mocker.AsyncMock() metagraph._set_weights_and_bonds = mocker.AsyncMock() + metagraph._get_all_stakes_from_chain = mocker.AsyncMock() return metagraph diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index e720aa52d7..ccf1c44589 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -11,9 +11,17 @@ def test_methods_comparable(mocker): subtensor = Subtensor(_mock=True) async_subtensor = AsyncSubtensor(_mock=True) + # methods which lives in subtensor only + excluded_subtensor_methods = ["wait_for_block"] + # methods which lives in async subtensor only excluded_async_subtensor_methods = ["initialize"] - subtensor_methods = [m for m in dir(subtensor) if not m.startswith("_")] + + subtensor_methods = [ + m + for m in dir(subtensor) + if not m.startswith("_") and m not in excluded_subtensor_methods + ] async_subtensor_methods = [ m