Skip to content

Commit 71d74f1

Browse files
authored
Merge pull request #2963 from opentensor/release/9.8.2
Release/9.8.2
2 parents d26b319 + 0cacd34 commit 71d74f1

File tree

12 files changed

+1719
-1084
lines changed

12 files changed

+1719
-1084
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## 9.8.2 /2025-07-10
4+
5+
## What's Changed
6+
* edit docstrings by @MichaelTrestman in https://github.com/opentensor/bittensor/pull/2951
7+
* fix and improve e2e stake fee test by @basfroman in https://github.com/opentensor/bittensor/pull/2959
8+
* Make DynamicInfo backwards compatible by @basfroman in https://github.com/opentensor/bittensor/pull/2958
9+
* Update staking fee logic by @basfroman in https://github.com/opentensor/bittensor/pull/2961
10+
11+
## New Contributors
12+
* @MichaelTrestman made their first contribution in https://github.com/opentensor/bittensor/pull/2951
13+
14+
**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.8.1...v9.8.2
15+
316
## 9.8.1 /2025-07-08
417

518
## What's Changed

bittensor/core/async_subtensor.py

Lines changed: 1234 additions & 904 deletions
Large diffs are not rendered by default.

bittensor/core/chain_data/dynamic_info.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,24 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo":
7575

7676
subnet_volume = Balance.from_rao(decoded["subnet_volume"]).set_unit(netuid)
7777

78-
if decoded.get("subnet_identity"):
78+
if subnet_identity := decoded.get("subnet_identity"):
79+
# we need to check it for keep backwards compatibility
80+
logo_bytes = subnet_identity.get("logo_url")
81+
si_logo_url = bytes(logo_bytes).decode() if logo_bytes else None
82+
7983
subnet_identity = SubnetIdentity(
80-
subnet_name=bytes(decoded["subnet_identity"]["subnet_name"]).decode(),
81-
github_repo=bytes(decoded["subnet_identity"]["github_repo"]).decode(),
82-
subnet_contact=bytes(
83-
decoded["subnet_identity"]["subnet_contact"]
84-
).decode(),
85-
subnet_url=bytes(decoded["subnet_identity"]["subnet_url"]).decode(),
86-
logo_url=bytes(decoded["subnet_identity"]["logo_url"]).decode(),
87-
discord=bytes(decoded["subnet_identity"]["discord"]).decode(),
88-
description=bytes(decoded["subnet_identity"]["description"]).decode(),
89-
additional=bytes(decoded["subnet_identity"]["additional"]).decode(),
84+
subnet_name=bytes(subnet_identity["subnet_name"]).decode(),
85+
github_repo=bytes(subnet_identity["github_repo"]).decode(),
86+
subnet_contact=bytes(subnet_identity["subnet_contact"]).decode(),
87+
subnet_url=bytes(subnet_identity["subnet_url"]).decode(),
88+
logo_url=si_logo_url,
89+
discord=bytes(subnet_identity["discord"]).decode(),
90+
description=bytes(subnet_identity["description"]).decode(),
91+
additional=bytes(subnet_identity["additional"]).decode(),
9092
)
9193
else:
9294
subnet_identity = None
95+
9396
price = decoded.get("price", None)
9497

9598
if price and not isinstance(price, Balance):
@@ -110,7 +113,11 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo":
110113
tao_in=tao_in,
111114
k=tao_in.rao * alpha_in.rao,
112115
is_dynamic=is_dynamic,
113-
price=price,
116+
price=(
117+
price
118+
if price is not None
119+
else Balance.from_tao(tao_in.tao / alpha_in.tao).set_unit(netuid)
120+
),
114121
alpha_out_emission=alpha_out_emission,
115122
alpha_in_emission=alpha_in_emission,
116123
tao_in_emission=tao_in_emission,

bittensor/core/extrinsics/asyncex/serving.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ async def get_metadata(
306306
block: Optional[int] = None,
307307
block_hash: Optional[str] = None,
308308
reuse_block: bool = False,
309-
) -> str:
309+
) -> Union[str, dict]:
310310
"""Fetches metadata from the blockchain for a given hotkey and netuid."""
311311
async with subtensor.substrate:
312312
block_hash = await subtensor.determine_block_hash(

bittensor/core/subtensor.py

Lines changed: 48 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,11 @@
120120
price_to_tick,
121121
LiquidityPosition,
122122
)
123-
from bittensor.utils.weight_utils import generate_weight_hash, convert_uids_and_weights
123+
from bittensor.utils.weight_utils import (
124+
generate_weight_hash,
125+
convert_uids_and_weights,
126+
U16_MAX,
127+
)
124128

125129
if TYPE_CHECKING:
126130
from bittensor_wallet import Wallet
@@ -445,15 +449,14 @@ def all_subnets(self, block: Optional[int] = None) -> Optional[list["DynamicInfo
445449
Optional[DynamicInfo]: A list of DynamicInfo objects, each containing detailed information about a subnet.
446450
447451
"""
448-
block_hash = self.determine_block_hash(block)
452+
block_hash = self.determine_block_hash(block=block)
449453
query = self.substrate.runtime_call(
450-
"SubnetInfoRuntimeApi",
451-
"get_all_dynamic_info",
454+
api="SubnetInfoRuntimeApi",
455+
method="get_all_dynamic_info",
452456
block_hash=block_hash,
453457
)
454458
subnet_prices = self.get_subnet_prices()
455459
decoded = query.decode()
456-
457460
for sn in decoded:
458461
sn.update({"price": subnet_prices.get(sn["netuid"], Balance.from_tao(0))})
459462
return DynamicInfo.list_from_dicts(decoded)
@@ -1277,13 +1280,9 @@ def get_hotkey_owner(
12771280
def get_minimum_required_stake(self) -> Balance:
12781281
"""
12791282
Returns the minimum required stake for nominators in the Subtensor network.
1280-
This method retries the substrate call up to three times with exponential backoff in case of failures.
12811283
12821284
Returns:
1283-
Balance: The minimum required stake as a Balance object.
1284-
1285-
Raises:
1286-
Exception: If the substrate call fails after the maximum number of retries.
1285+
The minimum required stake as a Balance object in TAO.
12871286
"""
12881287
result = self.substrate.query(
12891288
module="SubtensorModule", storage_function="NominatorMinRequiredStake"
@@ -1761,6 +1760,7 @@ def get_stake(
17611760

17621761
return Balance.from_rao(int(stake)).set_unit(netuid=netuid)
17631762

1763+
# TODO: remove unused parameters in SDK.v10
17641764
def get_stake_add_fee(
17651765
self,
17661766
amount: Balance,
@@ -1782,19 +1782,7 @@ def get_stake_add_fee(
17821782
Returns:
17831783
The calculated stake fee as a Balance object
17841784
"""
1785-
result = self.query_runtime_api(
1786-
runtime_api="StakeInfoRuntimeApi",
1787-
method="get_stake_fee",
1788-
params=[
1789-
None,
1790-
coldkey_ss58,
1791-
(hotkey_ss58, netuid),
1792-
coldkey_ss58,
1793-
amount.rao,
1794-
],
1795-
block=block,
1796-
)
1797-
return Balance.from_rao(result)
1785+
return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block)
17981786

17991787
def get_subnet_info(
18001788
self, netuid: int, block: Optional[int] = None
@@ -1887,6 +1875,7 @@ def get_subnet_prices(
18871875
prices.update({0: Balance.from_tao(1)})
18881876
return prices
18891877

1878+
# TODO: remove unused parameters in SDK.v10
18901879
def get_unstake_fee(
18911880
self,
18921881
amount: Balance,
@@ -1908,20 +1897,9 @@ def get_unstake_fee(
19081897
Returns:
19091898
The calculated stake fee as a Balance object
19101899
"""
1911-
result = self.query_runtime_api(
1912-
runtime_api="StakeInfoRuntimeApi",
1913-
method="get_stake_fee",
1914-
params=[
1915-
(hotkey_ss58, netuid),
1916-
coldkey_ss58,
1917-
None,
1918-
coldkey_ss58,
1919-
amount.rao,
1920-
],
1921-
block=block,
1922-
)
1923-
return Balance.from_rao(result)
1900+
return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block)
19241901

1902+
# TODO: remove unused parameters in SDK.v10
19251903
def get_stake_movement_fee(
19261904
self,
19271905
amount: Balance,
@@ -1949,19 +1927,9 @@ def get_stake_movement_fee(
19491927
Returns:
19501928
The calculated stake fee as a Balance object
19511929
"""
1952-
result = self.query_runtime_api(
1953-
runtime_api="StakeInfoRuntimeApi",
1954-
method="get_stake_fee",
1955-
params=[
1956-
(origin_hotkey_ss58, origin_netuid),
1957-
origin_coldkey_ss58,
1958-
(destination_hotkey_ss58, destination_netuid),
1959-
destination_coldkey_ss58,
1960-
amount.rao,
1961-
],
1962-
block=block,
1930+
return self.get_stake_operations_fee(
1931+
amount=amount, netuid=origin_netuid, block=block
19631932
)
1964-
return Balance.from_rao(result)
19651933

19661934
def get_stake_for_coldkey_and_hotkey(
19671935
self,
@@ -2049,6 +2017,31 @@ def get_stake_for_hotkey(
20492017

20502018
get_hotkey_stake = get_stake_for_hotkey
20512019

2020+
def get_stake_operations_fee(
2021+
self,
2022+
amount: Balance,
2023+
netuid: int,
2024+
block: Optional[int] = None,
2025+
):
2026+
"""Returns fee for any stake operation in specified subnet.
2027+
2028+
Args:
2029+
amount: Amount of stake to add in Alpha/TAO.
2030+
netuid: Netuid of subnet.
2031+
block: Block number at which to perform the calculation.
2032+
2033+
Returns:
2034+
The calculated stake fee as a Balance object.
2035+
"""
2036+
block_hash = self.determine_block_hash(block=block)
2037+
result = self.substrate.query(
2038+
module="Swap",
2039+
storage_function="FeeRate",
2040+
params=[netuid],
2041+
block_hash=block_hash,
2042+
)
2043+
return amount * (result.value / U16_MAX)
2044+
20522045
def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[Balance]:
20532046
"""
20542047
Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the
@@ -2700,17 +2693,20 @@ def subnet(self, netuid: int, block: Optional[int] = None) -> Optional[DynamicIn
27002693
Optional[DynamicInfo]: A DynamicInfo object, containing detailed information about a subnet.
27012694
27022695
"""
2703-
block_hash = self.determine_block_hash(block)
2696+
block_hash = self.determine_block_hash(block=block)
27042697

27052698
query = self.substrate.runtime_call(
2706-
"SubnetInfoRuntimeApi",
2707-
"get_dynamic_info",
2699+
api="SubnetInfoRuntimeApi",
2700+
method="get_dynamic_info",
27082701
params=[netuid],
27092702
block_hash=block_hash,
27102703
)
27112704

27122705
if isinstance(decoded := query.decode(), dict):
2713-
price = self.get_subnet_price(netuid=netuid, block=block)
2706+
try:
2707+
price = self.get_subnet_price(netuid=netuid, block=block)
2708+
except SubstrateRequestException:
2709+
price = None
27142710
return DynamicInfo.from_dict({**decoded, "price": price})
27152711
return None
27162712

bittensor/core/subtensor_api/staking.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]):
1919
)
2020
self.get_stake_info_for_coldkey = subtensor.get_stake_info_for_coldkey
2121
self.get_stake_movement_fee = subtensor.get_stake_movement_fee
22+
self.get_stake_operations_fee = subtensor.get_stake_operations_fee
2223
self.get_unstake_fee = subtensor.get_unstake_fee
2324
self.unstake = subtensor.unstake
2425
self.unstake_all = subtensor.unstake_all

bittensor/core/subtensor_api/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"):
8686
subtensor._subtensor.get_stake_info_for_coldkey
8787
)
8888
subtensor.get_stake_movement_fee = subtensor._subtensor.get_stake_movement_fee
89+
subtensor.get_stake_operations_fee = subtensor._subtensor.get_stake_operations_fee
8990
subtensor.get_subnet_burn_cost = subtensor._subtensor.get_subnet_burn_cost
9091
subtensor.get_subnet_hyperparameters = (
9192
subtensor._subtensor.get_subnet_hyperparameters

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "bittensor"
7-
version = "9.8.1"
7+
version = "9.8.2"
88
description = "Bittensor"
99
readme = "README.md"
1010
authors = [

tests/e2e_tests/test_delegate.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,13 @@ async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet):
293293
bittensor.logging.console.success("Test [green]test_delegates[/green] passed.")
294294

295295

296-
def test_nominator_min_required_stake(local_chain, subtensor, alice_wallet, bob_wallet):
296+
def test_nominator_min_required_stake(
297+
local_chain, subtensor, alice_wallet, bob_wallet, dave_wallet
298+
):
297299
"""
298300
Tests:
299301
- Check default NominatorMinRequiredStake
300-
- Add Stake to Nominate
302+
- Add Stake to Nominate from Dave to Bob
301303
- Update NominatorMinRequiredStake
302304
- Check Nominator is removed
303305
"""
@@ -323,15 +325,22 @@ def test_nominator_min_required_stake(local_chain, subtensor, alice_wallet, bob_
323325
assert minimum_required_stake == Balance(0)
324326

325327
subtensor.burned_register(
326-
bob_wallet,
327-
alice_subnet_netuid,
328+
wallet=bob_wallet,
329+
netuid=alice_subnet_netuid,
330+
wait_for_inclusion=True,
331+
wait_for_finalization=True,
332+
)
333+
334+
subtensor.burned_register(
335+
wallet=dave_wallet,
336+
netuid=alice_subnet_netuid,
328337
wait_for_inclusion=True,
329338
wait_for_finalization=True,
330339
)
331340

332341
success = subtensor.add_stake(
333-
alice_wallet,
334-
bob_wallet.hotkey.ss58_address,
342+
wallet=dave_wallet,
343+
hotkey_ss58=bob_wallet.hotkey.ss58_address,
335344
netuid=alice_subnet_netuid,
336345
amount=Balance.from_tao(10_000),
337346
wait_for_inclusion=True,
@@ -341,17 +350,17 @@ def test_nominator_min_required_stake(local_chain, subtensor, alice_wallet, bob_
341350
assert success is True
342351

343352
stake = subtensor.get_stake(
344-
alice_wallet.coldkey.ss58_address,
345-
bob_wallet.hotkey.ss58_address,
353+
coldkey_ss58=dave_wallet.coldkey.ss58_address,
354+
hotkey_ss58=bob_wallet.hotkey.ss58_address,
346355
netuid=alice_subnet_netuid,
347356
)
348357

349358
assert stake > 0
350359

351360
# this will trigger clear_small_nominations
352361
sudo_set_admin_utils(
353-
local_chain,
354-
alice_wallet,
362+
substrate=local_chain,
363+
wallet=alice_wallet,
355364
call_function="sudo_set_nominator_min_required_stake",
356365
call_params={
357366
"min_stake": "100000000000000",
@@ -363,8 +372,8 @@ def test_nominator_min_required_stake(local_chain, subtensor, alice_wallet, bob_
363372
assert minimum_required_stake == Balance.from_tao(100_000)
364373

365374
stake = subtensor.get_stake(
366-
alice_wallet.coldkey.ss58_address,
367-
bob_wallet.hotkey.ss58_address,
375+
coldkey_ss58=dave_wallet.coldkey.ss58_address,
376+
hotkey_ss58=bob_wallet.hotkey.ss58_address,
368377
netuid=alice_subnet_netuid,
369378
)
370379

0 commit comments

Comments
 (0)