diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 0887d5ef76..61789e1720 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -799,15 +799,15 @@ async def all_subnets( subnets = await subtensor.all_subnets() """ block_hash = await self.determine_block_hash( - block_number, block_hash, reuse_block + block=block_number, block_hash=block_hash, reuse_block=reuse_block ) if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash query, subnet_prices = await asyncio.gather( self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_all_dynamic_info", + api="SubnetInfoRuntimeApi", + method="get_all_dynamic_info", block_hash=block_hash, ), self.get_subnet_prices(), @@ -2616,9 +2616,7 @@ async def get_subnet_price( if netuid == 0: return Balance.from_tao(1) - block_hash = await self.determine_block_hash( - block=block, block_hash=block_hash, reuse_block=reuse_block - ) + block_hash = await self.determine_block_hash(block=block) current_sqrt_price = await self.substrate.query( module="Swap", storage_function="AlphaSqrtPrice", @@ -3729,20 +3727,32 @@ async def subnet( Returns: Optional[DynamicInfo]: A DynamicInfo object, containing detailed information about a subnet. """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + block_hash = await self.determine_block_hash( + block=block, block_hash=block_hash, reuse_block=reuse_block + ) if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - query = await self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_dynamic_info", - params=[netuid], - block_hash=block_hash, + query, price = await asyncio.gather( + self.substrate.runtime_call( + "SubnetInfoRuntimeApi", + "get_dynamic_info", + params=[netuid], + block_hash=block_hash, + ), + self.get_subnet_price( + netuid=netuid, + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ), + return_exceptions=True, ) if isinstance(decoded := query.decode(), dict): - price = self.get_subnet_price(netuid=netuid, block=block) + if isinstance(price, SubstrateRequestException): + price = None return DynamicInfo.from_dict({**decoded, "price": price}) return None diff --git a/bittensor/core/chain_data/dynamic_info.py b/bittensor/core/chain_data/dynamic_info.py index f0256c1ba5..504185832f 100644 --- a/bittensor/core/chain_data/dynamic_info.py +++ b/bittensor/core/chain_data/dynamic_info.py @@ -75,21 +75,24 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo": subnet_volume = Balance.from_rao(decoded["subnet_volume"]).set_unit(netuid) - if decoded.get("subnet_identity"): + if subnet_identity := decoded.get("subnet_identity"): + # we need to check it for keep backwards compatibility + logo_bytes = subnet_identity.get("logo_url") + si_logo_url = bytes(logo_bytes).decode() if logo_bytes else None + subnet_identity = SubnetIdentity( - subnet_name=bytes(decoded["subnet_identity"]["subnet_name"]).decode(), - github_repo=bytes(decoded["subnet_identity"]["github_repo"]).decode(), - subnet_contact=bytes( - decoded["subnet_identity"]["subnet_contact"] - ).decode(), - subnet_url=bytes(decoded["subnet_identity"]["subnet_url"]).decode(), - logo_url=bytes(decoded["subnet_identity"]["logo_url"]).decode(), - discord=bytes(decoded["subnet_identity"]["discord"]).decode(), - description=bytes(decoded["subnet_identity"]["description"]).decode(), - additional=bytes(decoded["subnet_identity"]["additional"]).decode(), + subnet_name=bytes(subnet_identity["subnet_name"]).decode(), + github_repo=bytes(subnet_identity["github_repo"]).decode(), + subnet_contact=bytes(subnet_identity["subnet_contact"]).decode(), + subnet_url=bytes(subnet_identity["subnet_url"]).decode(), + logo_url=si_logo_url, + discord=bytes(subnet_identity["discord"]).decode(), + description=bytes(subnet_identity["description"]).decode(), + additional=bytes(subnet_identity["additional"]).decode(), ) else: subnet_identity = None + price = decoded.get("price", None) if price and not isinstance(price, Balance): @@ -110,7 +113,11 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo": tao_in=tao_in, k=tao_in.rao * alpha_in.rao, is_dynamic=is_dynamic, - price=price, + price=( + price + if price is not None + else Balance.from_tao(tao_in.tao / alpha_in.tao).set_unit(netuid) + ), alpha_out_emission=alpha_out_emission, alpha_in_emission=alpha_in_emission, tao_in_emission=tao_in_emission, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index f2532ceb3f..a15d066bf5 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -445,15 +445,14 @@ def all_subnets(self, block: Optional[int] = None) -> Optional[list["DynamicInfo Optional[DynamicInfo]: A list of DynamicInfo objects, each containing detailed information about a subnet. """ - block_hash = self.determine_block_hash(block) + block_hash = self.determine_block_hash(block=block) query = self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_all_dynamic_info", + api="SubnetInfoRuntimeApi", + method="get_all_dynamic_info", block_hash=block_hash, ) subnet_prices = self.get_subnet_prices() decoded = query.decode() - for sn in decoded: sn.update({"price": subnet_prices.get(sn["netuid"], Balance.from_tao(0))}) return DynamicInfo.list_from_dicts(decoded) @@ -2696,17 +2695,20 @@ def subnet(self, netuid: int, block: Optional[int] = None) -> Optional[DynamicIn Optional[DynamicInfo]: A DynamicInfo object, containing detailed information about a subnet. """ - block_hash = self.determine_block_hash(block) + block_hash = self.determine_block_hash(block=block) query = self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_dynamic_info", + api="SubnetInfoRuntimeApi", + method="get_dynamic_info", params=[netuid], block_hash=block_hash, ) if isinstance(decoded := query.decode(), dict): - price = self.get_subnet_price(netuid=netuid, block=block) + try: + price = self.get_subnet_price(netuid=netuid, block=block) + except SubstrateRequestException: + price = None return DynamicInfo.from_dict({**decoded, "price": price}) return None diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 3016ae623d..f95d1e695d 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3833,9 +3833,7 @@ async def test_get_subnet_price(subtensor, mocker): ) # Asserts - mocked_determine_block_hash.assert_awaited_once_with( - block=None, block_hash=None, reuse_block=False - ) + mocked_determine_block_hash.assert_awaited_once_with(block=None) mocked_query.assert_awaited_once_with( module="Swap", storage_function="AlphaSqrtPrice", @@ -3875,3 +3873,85 @@ async def fake_current_sqrt_prices(): page_size=129, # total number of subnets ) assert result == expected_prices + + +@pytest.mark.asyncio +async def test_all_subnets(subtensor, mocker): + """Verify that `all_subnets` calls proper methods and returns the correct value.""" + # Preps + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_di_list_from_dicts = mocker.patch.object( + async_subtensor.DynamicInfo, "list_from_dicts" + ) + mocked_get_subnet_prices = mocker.patch.object( + subtensor, + "get_subnet_prices", + return_value={0: Balance.from_tao(1), 1: Balance.from_tao(0.029258617)}, + ) + mocked_decode = mocker.Mock(return_value=[{"netuid": 0}, {"netuid": 1}]) + mocked_runtime_call = mocker.Mock(decode=mocked_decode) + mocker.patch.object( + subtensor.substrate, "runtime_call", return_value=mocked_runtime_call + ) + + # Call + result = await subtensor.all_subnets() + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with( + block=None, block_hash=None, reuse_block=False + ) + subtensor.substrate.runtime_call.assert_called_once_with( + api="SubnetInfoRuntimeApi", + method="get_all_dynamic_info", + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_get_subnet_prices.assert_called_once() + mocked_di_list_from_dicts.assert_called_once_with( + [ + {"netuid": 0, "price": Balance.from_tao(1)}, + {"netuid": 1, "price": Balance.from_tao(0.029258617)}, + ] + ) + assert result == mocked_di_list_from_dicts.return_value + + +@pytest.mark.asyncio +async def test_subnet(subtensor, mocker): + """Verify that `subnet` calls proper methods and returns the correct value.""" + # Preps + netuid = 14 + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_di_from_dict = mocker.patch.object(async_subtensor.DynamicInfo, "from_dict") + mocked_get_subnet_price = mocker.patch.object( + subtensor, "get_subnet_price", return_value=Balance.from_tao(100.0) + ) + mocked_decode = mocker.Mock(return_value={"netuid": netuid}) + mocked_runtime_call = mocker.Mock(decode=mocked_decode) + mocker.patch.object( + subtensor.substrate, "runtime_call", return_value=mocked_runtime_call + ) + + # Call + result = await subtensor.subnet(netuid=netuid) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with( + block=None, block_hash=None, reuse_block=False + ) + subtensor.substrate.runtime_call.assert_awaited_once_with( + "SubnetInfoRuntimeApi", + "get_dynamic_info", + params=[netuid], + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_get_subnet_price.assert_awaited_once_with( + netuid=netuid, + block=None, + block_hash=mocked_determine_block_hash.return_value, + reuse_block=False, + ) + mocked_di_from_dict.assert_called_once_with( + {"netuid": netuid, "price": Balance.from_tao(100.0)} + ) + assert result == mocked_di_from_dict.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index e71f3507d0..f2d3d80b9d 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4195,3 +4195,74 @@ def test_get_subnet_prices(subtensor, mocker): page_size=129, # total number of subnets ) assert result == expected_prices + + +def test_all_subnets(subtensor, mocker): + """Verify that `all_subnets` calls proper methods and returns the correct value.""" + # Preps + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_di_list_from_dicts = mocker.patch.object( + subtensor_module.DynamicInfo, "list_from_dicts" + ) + mocked_get_subnet_prices = mocker.patch.object( + subtensor, + "get_subnet_prices", + return_value={0: Balance.from_tao(1), 1: Balance.from_tao(0.029258617)}, + ) + mocked_decode = mocker.Mock(return_value=[{"netuid": 0}, {"netuid": 1}]) + mocked_runtime_call = mocker.Mock(decode=mocked_decode) + mocker.patch.object( + subtensor.substrate, "runtime_call", return_value=mocked_runtime_call + ) + + # Call + result = subtensor.all_subnets() + + # Asserts + mocked_determine_block_hash.assert_called_once_with(block=None) + subtensor.substrate.runtime_call.assert_called_once_with( + api="SubnetInfoRuntimeApi", + method="get_all_dynamic_info", + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_get_subnet_prices.assert_called_once() + mocked_di_list_from_dicts.assert_called_once_with( + [ + {"netuid": 0, "price": Balance.from_tao(1)}, + {"netuid": 1, "price": Balance.from_tao(0.029258617)}, + ] + ) + assert result == mocked_di_list_from_dicts.return_value + + +def test_subnet(subtensor, mocker): + """Verify that `subnet` calls proper methods and returns the correct value.""" + # Preps + netuid = 14 + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_di_from_dict = mocker.patch.object(subtensor_module.DynamicInfo, "from_dict") + mocked_get_subnet_price = mocker.patch.object( + subtensor, "get_subnet_price", return_value=Balance.from_tao(100.0) + ) + mocked_decode = mocker.Mock(return_value={"netuid": netuid}) + mocked_runtime_call = mocker.Mock(decode=mocked_decode) + mocker.patch.object( + subtensor.substrate, "runtime_call", return_value=mocked_runtime_call + ) + + # Call + result = subtensor.subnet(netuid=netuid) + + # Asserts + subtensor.substrate.runtime_call.assert_called_once_with( + api="SubnetInfoRuntimeApi", + method="get_dynamic_info", + params=[netuid], + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_determine_block_hash.assert_called_once_with(block=None) + mocked_get_subnet_price.assert_called_once_with(netuid=netuid, block=None) + mocked_di_from_dict.assert_called_once_with( + {"netuid": netuid, "price": Balance.from_tao(100.0)} + ) + assert result == mocked_di_from_dict.return_value