Skip to content

Commit 5ccd5fa

Browse files
authored
Merge pull request #526 from opentensor/update/slippage-price-calcs
Update/slippage price calcs
2 parents 4ca8ff8 + 59ba1da commit 5ccd5fa

File tree

7 files changed

+229
-240
lines changed

7 files changed

+229
-240
lines changed

bittensor_cli/src/bittensor/subtensor_interface.py

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,23 +1423,40 @@ async def get_stake_for_coldkeys(
14231423
return stake_info_map if stake_info_map else None
14241424

14251425
async def all_subnets(self, block_hash: Optional[str] = None) -> list[DynamicInfo]:
1426-
result = await self.query_runtime_api(
1427-
"SubnetInfoRuntimeApi",
1428-
"get_all_dynamic_info",
1429-
block_hash=block_hash,
1426+
result, prices = await asyncio.gather(
1427+
self.query_runtime_api(
1428+
"SubnetInfoRuntimeApi",
1429+
"get_all_dynamic_info",
1430+
block_hash=block_hash,
1431+
),
1432+
self.get_subnet_prices(block_hash=block_hash, page_size=129),
14301433
)
1431-
return DynamicInfo.list_from_any(result)
1434+
sns: list[DynamicInfo] = DynamicInfo.list_from_any(result)
1435+
for sn in sns:
1436+
if sn.netuid == 0:
1437+
sn.price = Balance.from_tao(1.0)
1438+
else:
1439+
try:
1440+
sn.price = prices[sn.netuid]
1441+
except KeyError:
1442+
sn.price = sn.tao_in / sn.alpha_in
1443+
return sns
14321444

14331445
async def subnet(
14341446
self, netuid: int, block_hash: Optional[str] = None
14351447
) -> "DynamicInfo":
1436-
result = await self.query_runtime_api(
1437-
"SubnetInfoRuntimeApi",
1438-
"get_dynamic_info",
1439-
params=[netuid],
1440-
block_hash=block_hash,
1448+
result, price = await asyncio.gather(
1449+
self.query_runtime_api(
1450+
"SubnetInfoRuntimeApi",
1451+
"get_dynamic_info",
1452+
params=[netuid],
1453+
block_hash=block_hash,
1454+
),
1455+
self.get_subnet_price(netuid=netuid, block_hash=block_hash),
14411456
)
1442-
return DynamicInfo.from_any(result)
1457+
subnet_ = DynamicInfo.from_any(result)
1458+
subnet_.price = price
1459+
return subnet_
14431460

14441461
async def get_owned_hotkeys(
14451462
self,
@@ -1581,3 +1598,53 @@ async def get_coldkey_swap_schedule_duration(
15811598
)
15821599

15831600
return result
1601+
1602+
async def get_subnet_price(
1603+
self,
1604+
netuid: int = None,
1605+
block_hash: Optional[str] = None,
1606+
) -> Balance:
1607+
"""
1608+
Gets the current Alpha price in TAO for a specific subnet.
1609+
1610+
:param netuid: The unique identifier of the subnet.
1611+
:param block_hash: The hash of the block to retrieve the price from.
1612+
1613+
:return: The current Alpha price in TAO units for the specified subnet.
1614+
"""
1615+
current_sqrt_price = await self.query(
1616+
module="Swap",
1617+
storage_function="AlphaSqrtPrice",
1618+
params=[netuid],
1619+
block_hash=block_hash,
1620+
)
1621+
1622+
current_sqrt_price = fixed_to_float(current_sqrt_price)
1623+
current_price = current_sqrt_price * current_sqrt_price
1624+
return Balance.from_rao(int(current_price * 1e9))
1625+
1626+
async def get_subnet_prices(
1627+
self, block_hash: Optional[str] = None, page_size: int = 100
1628+
) -> dict[int, Balance]:
1629+
"""
1630+
Gets the current Alpha prices in TAO for all subnets.
1631+
1632+
:param block_hash: The hash of the block to retrieve prices from.
1633+
:param page_size: The page size for batch queries (default: 100).
1634+
1635+
:return: A dictionary mapping netuid to the current Alpha price in TAO units.
1636+
"""
1637+
query = await self.substrate.query_map(
1638+
module="Swap",
1639+
storage_function="AlphaSqrtPrice",
1640+
page_size=page_size,
1641+
block_hash=block_hash,
1642+
)
1643+
1644+
map_ = {}
1645+
async for netuid_, current_sqrt_price in query:
1646+
current_sqrt_price_ = fixed_to_float(current_sqrt_price.value)
1647+
current_price = current_sqrt_price_**2
1648+
map_[netuid_] = Balance.from_rao(int(current_price * 1e9))
1649+
1650+
return map_

bittensor_cli/src/commands/stake/add.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,12 @@ async def stake_extrinsic(
245245
# Get subnet data and stake information for coldkey
246246
chain_head = await subtensor.substrate.get_chain_head()
247247
_all_subnets, _stake_info, current_wallet_balance = await asyncio.gather(
248-
subtensor.all_subnets(),
248+
subtensor.all_subnets(block_hash=chain_head),
249249
subtensor.get_stake_for_coldkey(
250250
coldkey_ss58=wallet.coldkeypub.ss58_address,
251251
block_hash=chain_head,
252252
),
253-
subtensor.get_balance(wallet.coldkeypub.ss58_address),
253+
subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=chain_head),
254254
)
255255
all_subnets = {di.netuid: di for di in _all_subnets}
256256

@@ -307,6 +307,7 @@ async def stake_extrinsic(
307307
return False
308308
remaining_wallet_balance -= amount_to_stake
309309

310+
# TODO this should be asyncio gathered before the for loop
310311
stake_fee = await subtensor.get_stake_fee(
311312
origin_hotkey_ss58=None,
312313
origin_netuid=None,
@@ -318,14 +319,20 @@ async def stake_extrinsic(
318319
)
319320

320321
# Calculate slippage
321-
try:
322-
received_amount, slippage_pct, slippage_pct_float, rate = (
323-
_calculate_slippage(subnet_info, amount_to_stake, stake_fee)
324-
)
325-
except ValueError:
326-
return False
327-
328-
max_slippage = max(slippage_pct_float, max_slippage)
322+
# TODO: Update for V3, slippage calculation is significantly different in v3
323+
# try:
324+
# received_amount, slippage_pct, slippage_pct_float, rate = (
325+
# _calculate_slippage(subnet_info, amount_to_stake, stake_fee)
326+
# )
327+
# except ValueError:
328+
# return False
329+
#
330+
# max_slippage = max(slippage_pct_float, max_slippage)
331+
332+
# Temporary workaround - calculations without slippage
333+
current_price_float = float(subnet_info.price.tao)
334+
rate = 1.0 / current_price_float
335+
received_amount = rate * amount_to_stake
329336

330337
# Add rows for the table
331338
base_row = [
@@ -336,19 +343,19 @@ async def stake_extrinsic(
336343
+ f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
337344
str(received_amount.set_unit(netuid)), # received
338345
str(stake_fee), # fee
339-
str(slippage_pct), # slippage
346+
# str(slippage_pct), # slippage
340347
]
341348

342349
# If we are staking safe, add price tolerance
343350
if safe_staking:
344351
if subnet_info.is_dynamic:
345-
rate = amount_to_stake.rao / received_amount.rao
346-
_rate_with_tolerance = rate * (
347-
1 + rate_tolerance
352+
price_with_tolerance = current_price_float * (1 + rate_tolerance)
353+
_rate_with_tolerance = (
354+
1.0 / price_with_tolerance
348355
) # Rate only for display
349356
rate_with_tolerance = f"{_rate_with_tolerance:.4f}"
350357
price_with_tolerance = Balance.from_tao(
351-
_rate_with_tolerance
358+
price_with_tolerance
352359
).rao # Actual price to pass to extrinsic
353360
else:
354361
rate_with_tolerance = "1"
@@ -581,9 +588,10 @@ def _define_stake_table(
581588
justify="center",
582589
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
583590
)
584-
table.add_column(
585-
"Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
586-
)
591+
# TODO: Uncomment when slippage is reimplemented for v3
592+
# table.add_column(
593+
# "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
594+
# )
587595

588596
if safe_staking:
589597
table.add_column(
@@ -628,8 +636,8 @@ def _print_table_and_slippage(table: Table, max_slippage: float, safe_staking: b
628636
- [bold white]Hotkey[/bold white]: The ss58 address of the hotkey you are staking to.
629637
- [bold white]Amount[/bold white]: The TAO you are staking into this subnet onto this hotkey.
630638
- [bold white]Rate[/bold white]: The rate of exchange between your TAO and the subnet's stake.
631-
- [bold white]Received[/bold white]: The amount of stake you will receive on this subnet after slippage.
632-
- [bold white]Slippage[/bold white]: The slippage percentage of the stake operation. (0% if the subnet is not dynamic i.e. root)."""
639+
- [bold white]Received[/bold white]: The amount of stake you will receive on this subnet after slippage."""
640+
# - [bold white]Slippage[/bold white]: The slippage percentage of the stake operation. (0% if the subnet is not dynamic i.e. root)."""
633641

634642
safe_staking_description = """
635643
- [bold white]Rate Tolerance[/bold white]: Maximum acceptable alpha rate. If the rate exceeds this tolerance, the transaction will be limited or rejected.
@@ -654,6 +662,9 @@ def _calculate_slippage(
654662
- slippage_str: Formatted slippage percentage string
655663
- slippage_float: Raw slippage percentage value
656664
- rate: Exchange rate string
665+
666+
TODO: Update to v3. This method only works for protocol-liquidity-only
667+
mode (user liquidity disabled)
657668
"""
658669
amount_after_fee = amount - stake_fee
659670

@@ -670,6 +681,7 @@ def _calculate_slippage(
670681
slippage_str = f"{slippage_pct_float:.4f} %"
671682
rate = f"{(1 / subnet_info.price.tao or 1):.4f}"
672683
else:
684+
# TODO: Fix this. Slippage is always zero for static networks.
673685
slippage_pct_float = (
674686
100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0
675687
)

bittensor_cli/src/commands/stake/list.py

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,15 @@ def define_table(
111111
style=COLOR_PALETTE["POOLS"]["RATE"],
112112
justify="center",
113113
)
114-
defined_table.add_column(
115-
f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})",
116-
footer_style="overline white",
117-
style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"],
118-
justify="right",
119-
footer=f"τ {millify_tao(total_swapped_tao_value_.tao)}"
120-
if not verbose
121-
else f"{total_swapped_tao_value_}",
122-
)
114+
# defined_table.add_column(
115+
# f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})",
116+
# footer_style="overline white",
117+
# style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"],
118+
# justify="right",
119+
# footer=f"τ {millify_tao(total_swapped_tao_value_.tao)}"
120+
# if not verbose
121+
# else f"{total_swapped_tao_value_}",
122+
# )
123123
defined_table.add_column(
124124
"[white]Registered",
125125
style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
@@ -168,25 +168,17 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]):
168168
tao_value_ = pool.alpha_to_tao(alpha_value)
169169
total_tao_value_ += tao_value_
170170

171-
# Swapped TAO value and slippage cell
172-
swapped_tao_value_, _, slippage_percentage_ = (
173-
pool.alpha_to_tao_with_slippage(substake_.stake)
174-
)
175-
total_swapped_tao_value_ += swapped_tao_value_
176-
177-
# Slippage percentage cell
178-
if pool.is_dynamic:
179-
slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_percentage_:.3f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
180-
else:
181-
slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]0.000%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
171+
# TAO value cell
172+
tao_value_ = pool.alpha_to_tao(substake_.stake)
173+
total_swapped_tao_value_ += tao_value_
182174

183175
if netuid == 0:
184-
swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_percentage})"
176+
swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]"
185177
else:
186178
swap_value = (
187-
f"τ {millify_tao(swapped_tao_value_.tao)} ({slippage_percentage})"
179+
f"τ {millify_tao(tao_value_.tao)}"
188180
if not verbose
189-
else f"{swapped_tao_value_} ({slippage_percentage})"
181+
else f"{tao_value_}"
190182
)
191183

192184
# Per block emission cell
@@ -214,7 +206,7 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]):
214206
else f"{symbol} {stake_value}", # Stake (a)
215207
f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a)
216208
# f"τ {millify_tao(tao_ownership.tao)}" if not verbose else f"{tao_ownership}", # TAO equiv
217-
swap_value, # Swap(α) -> τ
209+
# swap_value, # Swap(α) -> τ
218210
"YES"
219211
if substake_.is_registered
220212
else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registered
@@ -232,7 +224,7 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]):
232224
"value": tao_value_.tao,
233225
"stake_value": substake_.stake.tao,
234226
"rate": pool.price.tao,
235-
"swap_value": swap_value,
227+
# "swap_value": swap_value,
236228
"registered": True if substake_.is_registered else False,
237229
"emission": {
238230
"alpha": per_block_emission,
@@ -317,9 +309,7 @@ def format_cell(
317309
alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid)
318310
tao_value_ = pool.alpha_to_tao(alpha_value)
319311
total_tao_value_ += tao_value_
320-
swapped_tao_value_, slippage, slippage_pct = (
321-
pool.alpha_to_tao_with_slippage(substake_.stake)
322-
)
312+
swapped_tao_value_ = pool.alpha_to_tao(substake_.stake)
323313
total_swapped_tao_value_ += swapped_tao_value_
324314

325315
# Store current values for future delta tracking
@@ -364,19 +354,16 @@ def format_cell(
364354
)
365355

366356
if netuid != 0:
367-
swap_cell = (
368-
format_cell(
369-
swapped_tao_value_.tao,
370-
prev.get("swapped_value"),
371-
unit="τ",
372-
unit_first_=True,
373-
precision=4,
374-
millify=True if not verbose else False,
375-
)
376-
+ f" ({slippage_pct:.2f}%)"
357+
swap_cell = format_cell(
358+
swapped_tao_value_.tao,
359+
prev.get("swapped_value"),
360+
unit="τ",
361+
unit_first_=True,
362+
precision=4,
363+
millify=True if not verbose else False,
377364
)
378365
else:
379-
swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_pct}%)"
366+
swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]"
380367

381368
emission_value = substake_.emission.tao / (pool.tempo or 1)
382369
emission_cell = format_cell(
@@ -408,7 +395,7 @@ def format_cell(
408395
exchange_cell, # Exchange value
409396
stake_cell, # Stake amount
410397
rate_cell, # Rate
411-
swap_cell, # Swap value with slippage
398+
# swap_cell, # Swap value
412399
"YES"
413400
if substake_.is_registered
414401
else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registration status
@@ -591,12 +578,12 @@ def format_cell(
591578
f"Wallet:\n"
592579
f" Coldkey SS58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{coldkey_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n"
593580
f" Free Balance: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n"
594-
f" Total TAO Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n"
595-
f" Total TAO Swapped Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_swapped_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]"
581+
f" Total TAO Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]"
582+
# f"\n Total TAO Swapped Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_swapped_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]"
596583
)
597584
dict_output["free_balance"] = balance.tao
598585
dict_output["total_tao_value"] = all_hks_tao_value.tao
599-
dict_output["total_swapped_tao_value"] = all_hks_swapped_tao_value.tao
586+
# dict_output["total_swapped_tao_value"] = all_hks_swapped_tao_value.tao
600587
if json_output:
601588
json_console.print(json.dumps(dict_output))
602589
if not sub_stakes:
@@ -658,12 +645,12 @@ def format_cell(
658645
),
659646
(
660647
"[bold tan]Exchange Value (α x τ/α)[/bold tan]",
661-
"This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].",
662-
),
663-
(
664-
"[bold tan]Swap (α → τ)[/bold tan]",
665-
"This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].",
648+
"This is the potential τ you will receive if you unstake from this hotkey now on this subnet. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].",
666649
),
650+
# (
651+
# "[bold tan]Swap (α → τ)[/bold tan]",
652+
# "This is the τ you will receive if you unstake from this hotkey now on this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].",
653+
# ),
667654
(
668655
"[bold tan]Registered[/bold tan]",
669656
"Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].",

0 commit comments

Comments
 (0)