Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 13 additions & 16 deletions bittensor/core/async_subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4802,26 +4802,23 @@ async def get_subnet_prices(
Notes:
Subnet 0 (root network) always has a price of 1 TAO since it uses TAO directly rather than Alpha.
"""
block_hash = await self.determine_block_hash(
# TODO: we will maintain this logic until we receive a function that returns all subnet prices in the chain as a
# single call.
block_hash = await self.determine_block_hash(block, block_hash)
prices = {0: Balance.from_tao(1)}
netuids = await self.get_all_subnets_netuid(
block=block, block_hash=block_hash, reuse_block=reuse_block
)

current_sqrt_prices = await self.substrate.query_map(
module="Swap",
storage_function="AlphaSqrtPrice",
block_hash=block_hash,
page_size=129, # total number of subnets
)

prices = {}
async for id_, current_sqrt_price in current_sqrt_prices:
current_sqrt_price = fixed_to_float(current_sqrt_price)
current_price = current_sqrt_price * current_sqrt_price
current_price_in_tao = Balance.from_rao(int(current_price * 1e9))
prices.update({id_: current_price_in_tao})
tasks = [
self.get_subnet_price(
netuid, block=block, block_hash=block_hash, reuse_block=reuse_block
)
for netuid in netuids
]

# SN0 price is always 1 TAO
prices.update({0: Balance.from_tao(1)})
prices_list = await asyncio.gather(*tasks)
prices.update(dict(zip(netuids, prices_list)))
return prices

async def get_subnet_reveal_period_epochs(
Expand Down
25 changes: 25 additions & 0 deletions bittensor/core/extrinsics/asyncex/liquidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from bittensor.core.extrinsics.pallets import Swap
from bittensor.core.settings import DEFAULT_MEV_PROTECTION
from bittensor.core.types import ExtrinsicResponse
from bittensor.utils import ChainFeatureDisabledWarning, deprecated_message
from bittensor.utils.balance import Balance
from bittensor.utils.liquidity import price_to_tick

Expand Down Expand Up @@ -56,6 +57,12 @@ async def add_liquidity_extrinsic(
Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call
`toggle_user_liquidity_extrinsic` to enable/disable user liquidity.
"""
deprecated_message(
message="User liquidity is currently disabled on the chain. "
"Calling this method will result in a 'UserLiquidityDisabled' error.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
try:
unlock_type = "coldkey" if hotkey_ss58 else "both"
if not (
Expand Down Expand Up @@ -138,6 +145,12 @@ async def modify_liquidity_extrinsic(
Note: Modifying is allowed even when user liquidity is enabled in specified subnet.
Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity.
"""
deprecated_message(
message="User liquidity is currently disabled on the chain. "
"Calling this method will result in a 'UserLiquidityDisabled' error.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
try:
unlock_type = "coldkey" if hotkey_ss58 else "both"
if not (
Expand Down Expand Up @@ -217,6 +230,12 @@ async def remove_liquidity_extrinsic(
Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call
`toggle_user_liquidity_extrinsic` to enable/disable user liquidity.
"""
deprecated_message(
message="User liquidity is currently disabled on the chain. "
"Calling this method will result in a 'UserLiquidityDisabled' error.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
try:
unlock_type = "coldkey" if hotkey_ss58 else "both"
if not (
Expand Down Expand Up @@ -290,6 +309,12 @@ async def toggle_user_liquidity_extrinsic(
Returns:
ExtrinsicResponse: The result object of the extrinsic execution.
"""
deprecated_message(
message="User liquidity is currently disabled on the chain. "
"Calling this method will result in a 'UserLiquidityDisabled' error.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
try:
if not (
unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error)
Expand Down
25 changes: 25 additions & 0 deletions bittensor/core/extrinsics/liquidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from bittensor.core.extrinsics.pallets import Swap
from bittensor.core.settings import DEFAULT_MEV_PROTECTION
from bittensor.core.types import ExtrinsicResponse
from bittensor.utils import ChainFeatureDisabledWarning, deprecated_message
from bittensor.utils.balance import Balance
from bittensor.utils.liquidity import price_to_tick

Expand Down Expand Up @@ -56,6 +57,12 @@ def add_liquidity_extrinsic(
Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call
`toggle_user_liquidity_extrinsic` to enable/disable user liquidity.
"""
deprecated_message(
message="User liquidity is currently disabled on the chain. "
"Calling this method will result in a 'UserLiquidityDisabled' error.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
try:
unlock_type = "coldkey" if hotkey_ss58 else "both"
if not (
Expand Down Expand Up @@ -138,6 +145,12 @@ def modify_liquidity_extrinsic(
Note: Modifying is allowed even when user liquidity is enabled in specified subnet. Call
`toggle_user_liquidity_extrinsic` to enable/disable user liquidity.
"""
deprecated_message(
message="User liquidity is currently disabled on the chain. "
"Calling this method will result in a 'UserLiquidityDisabled' error.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
try:
unlock_type = "coldkey" if hotkey_ss58 else "both"
if not (
Expand Down Expand Up @@ -217,6 +230,12 @@ def remove_liquidity_extrinsic(
Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call
`toggle_user_liquidity_extrinsic` to enable/disable user liquidity.
"""
deprecated_message(
message="User liquidity is currently disabled on the chain. "
"Calling this method will result in a 'UserLiquidityDisabled' error.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
try:
unlock_type = "coldkey" if hotkey_ss58 else "both"
if not (
Expand Down Expand Up @@ -290,6 +309,12 @@ def toggle_user_liquidity_extrinsic(
Returns:
ExtrinsicResponse: The result object of the extrinsic execution.
"""
deprecated_message(
message="User liquidity is currently disabled on the chain. "
"Calling this method will result in a 'UserLiquidityDisabled' error.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
try:
if not (
unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error)
Expand Down
23 changes: 6 additions & 17 deletions bittensor/core/subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3939,24 +3939,13 @@ def get_subnet_prices(
Notes:
Subnet 0 (root network) always has a price of 1 TAO since it uses TAO directly rather than Alpha.
"""
block_hash = self.determine_block_hash(block=block)

current_sqrt_prices = self.substrate.query_map(
module="Swap",
storage_function="AlphaSqrtPrice",
block_hash=block_hash,
page_size=129, # total number of subnets
# TODO: we will maintain this logic until we receive a function that returns all subnet prices in the chain as a
# single call.
prices = {0: Balance.from_tao(1)}
netuids = self.get_all_subnets_netuid(block=block)
prices.update(
{netuid: self.get_subnet_price(netuid, block=block) for netuid in netuids}
)

prices = {}
for id_, current_sqrt_price in current_sqrt_prices:
current_sqrt_price = fixed_to_float(current_sqrt_price)
current_price = current_sqrt_price * current_sqrt_price
current_price_in_tao = Balance.from_rao(int(current_price * 1e9))
prices.update({id_: current_price_in_tao})

# SN0 price is always 1 TAO
prices.update({0: Balance.from_tao(1)})
return prices

def get_subnet_reveal_period_epochs(
Expand Down
31 changes: 26 additions & 5 deletions bittensor/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import inspect
import warnings
from collections import namedtuple
from typing import Any, Literal, Union, Optional, TYPE_CHECKING
from typing import Any, Literal, Union, Optional, Type, TYPE_CHECKING
from urllib.parse import urlparse

import scalecodec
Expand Down Expand Up @@ -42,6 +42,14 @@

UnlockStatus = namedtuple("UnlockStatus", ["success", "message"])


class ChainFeatureDisabledWarning(UserWarning):
"""Warning indicating that a feature is currently disabled on the chain side.

This warning is issued when SDK functionality depends on chain feats that are temporarily unavailable or disabled.
"""


# redundant aliases
logging = logging
torch = torch
Expand Down Expand Up @@ -480,16 +488,29 @@ def determine_chain_endpoint_and_network(
return "unknown", network


def deprecated_message(message: str = False, replacement_message: str = False) -> None:
"""Shows a deprecation warning message with the given message."""
def deprecated_message(
message: Optional[str] = None,
replacement_message: Optional[str] = None,
category: Type[Warning] = DeprecationWarning,
stacklevel: int = 2,
) -> None:
"""Shows a warning message with the given message.

Parameters:
message: The warning message to display. If None, a default deprecation message is generated.
replacement_message: An optional additional message suggesting a replacement.
category: The warning category to use. Defaults to DeprecationWarning.
stacklevel: The stack level for the warning. Defaults to 2 (points to the caller of deprecated_message).
Increase this value if deprecated_message is called from within another wrapper function.
"""
message = (
message
if message
else f"The called object ({get_caller_name()}) is deprecated and will be removed in a future release."
)
message = f"{message} {replacement_message}" if replacement_message else message
warnings.simplefilter("default", DeprecationWarning)
warnings.warn(message=message, category=DeprecationWarning, stacklevel=2)
warnings.simplefilter("default", category)
warnings.warn(message=message, category=category, stacklevel=stacklevel)


def get_function_name() -> str:
Expand Down
41 changes: 40 additions & 1 deletion bittensor/utils/liquidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import Any
from dataclasses import dataclass

from bittensor.utils import ChainFeatureDisabledWarning, deprecated_message
from bittensor.utils.balance import Balance, fixed_to_float

# These three constants are unchangeable at the level of Uniswap math
Expand All @@ -26,6 +27,14 @@ class LiquidityPosition:
fees_alpha: Balance # RAO
netuid: int

def __post_init__(self):
deprecated_message(
message="LiquidityPosition is deprecated. User liquidity functionality has been "
"disabled on the chain after migration from Uniswap V3 to PalSwap.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)

def to_token_amounts(
self, current_subnet_price: Balance
) -> tuple[Balance, Balance]:
Expand Down Expand Up @@ -63,6 +72,12 @@ def to_token_amounts(

def price_to_tick(price: float) -> int:
"""Converts a float price to the nearest Uniswap V3 tick index."""
deprecated_message(
message="price_to_tick() is deprecated. The chain has migrated from Uniswap V3 "
"to PalSwap which does not use tick-based pricing.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
if price <= 0:
raise ValueError(f"Price must be positive, got `{price}`.")

Expand All @@ -77,6 +92,12 @@ def price_to_tick(price: float) -> int:

def tick_to_price(tick: int) -> float:
"""Convert an integer Uniswap V3 tick index to float price."""
deprecated_message(
message="tick_to_price() is deprecated. The chain has migrated from Uniswap V3 "
"to PalSwap which does not use tick-based pricing.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
if not (MIN_TICK <= tick <= MAX_TICK):
raise ValueError("Tick is out of allowed range")
return PRICE_STEP**tick
Expand All @@ -92,6 +113,12 @@ def get_fees(
above: bool,
) -> float:
"""Returns the liquidity fee."""
deprecated_message(
message="get_fees() is deprecated. The chain has migrated from Uniswap V3 "
"to PalSwap which uses a different fee calculation mechanism.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
tick_fee_key = "fees_out_tao" if quote else "fees_out_alpha"
tick_fee_value = fixed_to_float(tick.get(tick_fee_key))
global_fee_value = global_fees_tao if quote else global_fees_alpha
Expand All @@ -117,11 +144,16 @@ def get_fees_in_range(
fees_above_high: float,
) -> float:
"""Returns the liquidity fee value in a range."""
deprecated_message(
message="get_fees_in_range() is deprecated. The chain has migrated from Uniswap V3 "
"to PalSwap which uses a different fee calculation mechanism.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
global_fees = global_fees_tao if quote else global_fees_alpha
return global_fees - fees_below_low - fees_above_high


# Calculate fees for a position
def calculate_fees(
position: dict[str, Any],
global_fees_tao: float,
Expand All @@ -132,6 +164,13 @@ def calculate_fees(
alpha_fees_above_high: float,
netuid: int,
) -> tuple[Balance, Balance]:
"""Calculate fees for a position."""
deprecated_message(
message="calculate_fees() is deprecated. The chain has migrated from Uniswap V3 "
"to PalSwap which uses a different fee calculation mechanism.",
category=ChainFeatureDisabledWarning,
stacklevel=3,
)
fee_tao_agg = get_fees_in_range(
quote=True,
global_fees_tao=global_fees_tao,
Expand Down
19 changes: 10 additions & 9 deletions tests/integration_tests/test_subtensor_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ async def prepare_test(mocker, seed, **subtensor_args):
return subtensor


@pytest.mark.asyncio
async def test_get_all_subnets_info(mocker):
subtensor = await prepare_test(mocker, "get_all_subnets_info")
result = subtensor.get_all_subnets_info()
assert isinstance(result, list)
assert result[0].owner_ss58 == "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"
assert result[1].kappa == 32767
assert result[1].max_weight_limit == 65535
assert result[1].blocks_since_epoch == 30
# TODO: update test after Runtime updated
# @pytest.mark.asyncio
# async def test_get_all_subnets_info(mocker):
# subtensor = await prepare_test(mocker, "get_all_subnets_info")
# result = subtensor.get_all_subnets_info()
# assert isinstance(result, list)
# assert result[0].owner_ss58 == "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"
# assert result[1].kappa == 32767
# assert result[1].max_weight_limit == 65535
# assert result[1].blocks_since_epoch == 30


@pytest.mark.asyncio
Expand Down
Loading