Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8f3331e
edit docstrings
MichaelTrestman Jul 8, 2025
d93124d
edit docstrings
MichaelTrestman Jul 8, 2025
0f4632f
edit docstrings
MichaelTrestman Jul 8, 2025
dadd03b
edit docstrings
MichaelTrestman Jul 8, 2025
4cfe086
ruff
Jul 8, 2025
dfa477e
edit docstrings
MichaelTrestman Jul 8, 2025
3170994
edit docstrings
MichaelTrestman Jul 8, 2025
76a9784
docstrings edits
MichaelTrestman Jul 8, 2025
f1fc036
edit docstrings
MichaelTrestman Jul 8, 2025
7374e2b
edit docstrings
MichaelTrestman Jul 8, 2025
a47c24d
edit docstrings
MichaelTrestman Jul 8, 2025
e69530a
edit docstrings
MichaelTrestman Jul 8, 2025
ea4e956
edit docstrings
MichaelTrestman Jul 8, 2025
fe51378
Merge branch 'staging' into update-docstrings-asyncsubtensor
basfroman Jul 9, 2025
5fba86b
Merge branch 'staging' into update-docstrings-asyncsubtensor
basfroman Jul 9, 2025
3c31fe8
edit docstrings
MichaelTrestman Jul 9, 2025
5ba0adf
edit docstrings
MichaelTrestman Jul 9, 2025
3e7cc71
edit docstrings
MichaelTrestman Jul 9, 2025
b78498c
edit docstrings
MichaelTrestman Jul 9, 2025
dacb11e
edit docstrings
MichaelTrestman Jul 9, 2025
c6d08c9
edit docstrings
MichaelTrestman Jul 9, 2025
2794155
Update bittensor/core/async_subtensor.py
basfroman Jul 9, 2025
004c025
fix annotations, update docstrings
Jul 9, 2025
8ac6605
Merge pull request #2951 from opentensor/update-docstrings-asyncsubte…
MichaelTrestman Jul 9, 2025
0fb502b
make DynamicInfo backwards compatible with old blocks
Jul 9, 2025
ea4b1fe
add await, improve logic async
Jul 9, 2025
6b6fbd9
improve sunc logic
Jul 9, 2025
bf70ddd
add unit tests
Jul 9, 2025
5b37b44
fix and improve test
Jul 10, 2025
e500398
update docstring for `get_minimum_required_stake`
Jul 10, 2025
52b4853
add Dave wallet to test
Jul 10, 2025
3d1f463
update message
Jul 10, 2025
b7ff897
fix review comments
Jul 10, 2025
97ccae7
Merge pull request #2959 from opentensor/fix/roman/stake-fee-e2e-test
basfroman Jul 10, 2025
34cdc79
Merge branch 'staging' into bug/roman/make-dynamic-info-backwards-com…
basfroman Jul 10, 2025
7005f9a
fix unit test
Jul 10, 2025
c9bc6e2
Merge remote-tracking branch 'origin/bug/roman/make-dynamic-info-back…
Jul 10, 2025
af5226e
Merge pull request #2958 from opentensor/bug/roman/make-dynamic-info-…
basfroman Jul 10, 2025
033dd88
add `get_stake_operations_fee` amd update stake methods
Jul 10, 2025
de95e32
update SubtensorApi
Jul 10, 2025
fae12ef
add unit tests
Jul 10, 2025
6589832
update e2e fee test
Jul 10, 2025
333ca11
ruff
Jul 10, 2025
a6b9ebe
Merge pull request #2961 from opentensor/feat/roman/update-fee-logic
basfroman Jul 10, 2025
9f1af3e
bumps version and changelog
ibraheem-abe Jul 10, 2025
d0189e1
Merge pull request #2962 from opentensor/changelog/982
ibraheem-abe Jul 10, 2025
0cacd34
Merge branch 'master' into release/9.8.2
ibraheem-abe Jul 10, 2025
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 9.8.2 /2025-07-10

## What's Changed
* edit docstrings by @MichaelTrestman in https://github.com/opentensor/bittensor/pull/2951
* fix and improve e2e stake fee test by @basfroman in https://github.com/opentensor/bittensor/pull/2959
* Make DynamicInfo backwards compatible by @basfroman in https://github.com/opentensor/bittensor/pull/2958
* Update staking fee logic by @basfroman in https://github.com/opentensor/bittensor/pull/2961

## New Contributors
* @MichaelTrestman made their first contribution in https://github.com/opentensor/bittensor/pull/2951

**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.8.1...v9.8.2

## 9.8.1 /2025-07-08

## What's Changed
Expand Down
2,138 changes: 1,234 additions & 904 deletions bittensor/core/async_subtensor.py

Large diffs are not rendered by default.

31 changes: 19 additions & 12 deletions bittensor/core/chain_data/dynamic_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion bittensor/core/extrinsics/asyncex/serving.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ async def get_metadata(
block: Optional[int] = None,
block_hash: Optional[str] = None,
reuse_block: bool = False,
) -> str:
) -> Union[str, dict]:
"""Fetches metadata from the blockchain for a given hotkey and netuid."""
async with subtensor.substrate:
block_hash = await subtensor.determine_block_hash(
Expand Down
100 changes: 48 additions & 52 deletions bittensor/core/subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@
price_to_tick,
LiquidityPosition,
)
from bittensor.utils.weight_utils import generate_weight_hash, convert_uids_and_weights
from bittensor.utils.weight_utils import (
generate_weight_hash,
convert_uids_and_weights,
U16_MAX,
)

if TYPE_CHECKING:
from bittensor_wallet import Wallet
Expand Down Expand Up @@ -445,15 +449,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)
Expand Down Expand Up @@ -1277,13 +1280,9 @@ def get_hotkey_owner(
def get_minimum_required_stake(self) -> Balance:
"""
Returns the minimum required stake for nominators in the Subtensor network.
This method retries the substrate call up to three times with exponential backoff in case of failures.

Returns:
Balance: The minimum required stake as a Balance object.

Raises:
Exception: If the substrate call fails after the maximum number of retries.
The minimum required stake as a Balance object in TAO.
"""
result = self.substrate.query(
module="SubtensorModule", storage_function="NominatorMinRequiredStake"
Expand Down Expand Up @@ -1761,6 +1760,7 @@ def get_stake(

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

# TODO: remove unused parameters in SDK.v10
def get_stake_add_fee(
self,
amount: Balance,
Expand All @@ -1782,19 +1782,7 @@ def get_stake_add_fee(
Returns:
The calculated stake fee as a Balance object
"""
result = self.query_runtime_api(
runtime_api="StakeInfoRuntimeApi",
method="get_stake_fee",
params=[
None,
coldkey_ss58,
(hotkey_ss58, netuid),
coldkey_ss58,
amount.rao,
],
block=block,
)
return Balance.from_rao(result)
return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block)

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

# TODO: remove unused parameters in SDK.v10
def get_unstake_fee(
self,
amount: Balance,
Expand All @@ -1908,20 +1897,9 @@ def get_unstake_fee(
Returns:
The calculated stake fee as a Balance object
"""
result = self.query_runtime_api(
runtime_api="StakeInfoRuntimeApi",
method="get_stake_fee",
params=[
(hotkey_ss58, netuid),
coldkey_ss58,
None,
coldkey_ss58,
amount.rao,
],
block=block,
)
return Balance.from_rao(result)
return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block)

# TODO: remove unused parameters in SDK.v10
def get_stake_movement_fee(
self,
amount: Balance,
Expand Down Expand Up @@ -1949,19 +1927,9 @@ def get_stake_movement_fee(
Returns:
The calculated stake fee as a Balance object
"""
result = self.query_runtime_api(
runtime_api="StakeInfoRuntimeApi",
method="get_stake_fee",
params=[
(origin_hotkey_ss58, origin_netuid),
origin_coldkey_ss58,
(destination_hotkey_ss58, destination_netuid),
destination_coldkey_ss58,
amount.rao,
],
block=block,
return self.get_stake_operations_fee(
amount=amount, netuid=origin_netuid, block=block
)
return Balance.from_rao(result)

def get_stake_for_coldkey_and_hotkey(
self,
Expand Down Expand Up @@ -2049,6 +2017,31 @@ def get_stake_for_hotkey(

get_hotkey_stake = get_stake_for_hotkey

def get_stake_operations_fee(
self,
amount: Balance,
netuid: int,
block: Optional[int] = None,
):
"""Returns fee for any stake operation in specified subnet.

Args:
amount: Amount of stake to add in Alpha/TAO.
netuid: Netuid of subnet.
block: Block number at which to perform the calculation.

Returns:
The calculated stake fee as a Balance object.
"""
block_hash = self.determine_block_hash(block=block)
result = self.substrate.query(
module="Swap",
storage_function="FeeRate",
params=[netuid],
block_hash=block_hash,
)
return amount * (result.value / U16_MAX)

def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[Balance]:
"""
Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the
Expand Down Expand Up @@ -2700,17 +2693,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

Expand Down
1 change: 1 addition & 0 deletions bittensor/core/subtensor_api/staking.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]):
)
self.get_stake_info_for_coldkey = subtensor.get_stake_info_for_coldkey
self.get_stake_movement_fee = subtensor.get_stake_movement_fee
self.get_stake_operations_fee = subtensor.get_stake_operations_fee
self.get_unstake_fee = subtensor.get_unstake_fee
self.unstake = subtensor.unstake
self.unstake_all = subtensor.unstake_all
Expand Down
1 change: 1 addition & 0 deletions bittensor/core/subtensor_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"):
subtensor._subtensor.get_stake_info_for_coldkey
)
subtensor.get_stake_movement_fee = subtensor._subtensor.get_stake_movement_fee
subtensor.get_stake_operations_fee = subtensor._subtensor.get_stake_operations_fee
subtensor.get_subnet_burn_cost = subtensor._subtensor.get_subnet_burn_cost
subtensor.get_subnet_hyperparameters = (
subtensor._subtensor.get_subnet_hyperparameters
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "bittensor"
version = "9.8.1"
version = "9.8.2"
description = "Bittensor"
readme = "README.md"
authors = [
Expand Down
33 changes: 21 additions & 12 deletions tests/e2e_tests/test_delegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,13 @@ async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet):
bittensor.logging.console.success("Test [green]test_delegates[/green] passed.")


def test_nominator_min_required_stake(local_chain, subtensor, alice_wallet, bob_wallet):
def test_nominator_min_required_stake(
local_chain, subtensor, alice_wallet, bob_wallet, dave_wallet
):
"""
Tests:
- Check default NominatorMinRequiredStake
- Add Stake to Nominate
- Add Stake to Nominate from Dave to Bob
- Update NominatorMinRequiredStake
- Check Nominator is removed
"""
Expand All @@ -323,15 +325,22 @@ def test_nominator_min_required_stake(local_chain, subtensor, alice_wallet, bob_
assert minimum_required_stake == Balance(0)

subtensor.burned_register(
bob_wallet,
alice_subnet_netuid,
wallet=bob_wallet,
netuid=alice_subnet_netuid,
wait_for_inclusion=True,
wait_for_finalization=True,
)

subtensor.burned_register(
wallet=dave_wallet,
netuid=alice_subnet_netuid,
wait_for_inclusion=True,
wait_for_finalization=True,
)

success = subtensor.add_stake(
alice_wallet,
bob_wallet.hotkey.ss58_address,
wallet=dave_wallet,
hotkey_ss58=bob_wallet.hotkey.ss58_address,
netuid=alice_subnet_netuid,
amount=Balance.from_tao(10_000),
wait_for_inclusion=True,
Expand All @@ -341,17 +350,17 @@ def test_nominator_min_required_stake(local_chain, subtensor, alice_wallet, bob_
assert success is True

stake = subtensor.get_stake(
alice_wallet.coldkey.ss58_address,
bob_wallet.hotkey.ss58_address,
coldkey_ss58=dave_wallet.coldkey.ss58_address,
hotkey_ss58=bob_wallet.hotkey.ss58_address,
netuid=alice_subnet_netuid,
)

assert stake > 0

# this will trigger clear_small_nominations
sudo_set_admin_utils(
local_chain,
alice_wallet,
substrate=local_chain,
wallet=alice_wallet,
call_function="sudo_set_nominator_min_required_stake",
call_params={
"min_stake": "100000000000000",
Expand All @@ -363,8 +372,8 @@ def test_nominator_min_required_stake(local_chain, subtensor, alice_wallet, bob_
assert minimum_required_stake == Balance.from_tao(100_000)

stake = subtensor.get_stake(
alice_wallet.coldkey.ss58_address,
bob_wallet.hotkey.ss58_address,
coldkey_ss58=dave_wallet.coldkey.ss58_address,
hotkey_ss58=bob_wallet.hotkey.ss58_address,
netuid=alice_subnet_netuid,
)

Expand Down
Loading
Loading