Skip to content

Commit a6b9ebe

Browse files
authored
Merge pull request #2961 from opentensor/feat/roman/update-fee-logic
Update staking fee logic
2 parents af5226e + 333ca11 commit a6b9ebe

File tree

7 files changed

+293
-167
lines changed

7 files changed

+293
-167
lines changed

bittensor/core/async_subtensor.py

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@
117117
price_to_tick,
118118
LiquidityPosition,
119119
)
120-
from bittensor.utils.weight_utils import generate_weight_hash, convert_uids_and_weights
120+
from bittensor.utils.weight_utils import (
121+
generate_weight_hash,
122+
convert_uids_and_weights,
123+
U16_MAX,
124+
)
121125

122126
if TYPE_CHECKING:
123127
from async_substrate_interface.types import ScaleObj
@@ -2522,6 +2526,7 @@ async def get_stake(
25222526

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

2529+
# TODO: remove unused parameters in SDK.v10
25252530
async def get_stake_add_fee(
25262531
self,
25272532
amount: Balance,
@@ -2543,19 +2548,9 @@ async def get_stake_add_fee(
25432548
Returns:
25442549
The calculated stake fee as a Balance object
25452550
"""
2546-
result = await self.query_runtime_api(
2547-
runtime_api="StakeInfoRuntimeApi",
2548-
method="get_stake_fee",
2549-
params=[
2550-
None,
2551-
coldkey_ss58,
2552-
(hotkey_ss58, netuid),
2553-
coldkey_ss58,
2554-
amount.rao,
2555-
],
2556-
block=block,
2551+
return await self.get_stake_operations_fee(
2552+
amount=amount, netuid=netuid, block=block
25572553
)
2558-
return Balance.from_rao(result)
25592554

25602555
async def get_subnet_info(
25612556
self,
@@ -2669,6 +2664,7 @@ async def get_subnet_prices(
26692664
prices.update({0: Balance.from_tao(1)})
26702665
return prices
26712666

2667+
# TODO: remove unused parameters in SDK.v10
26722668
async def get_unstake_fee(
26732669
self,
26742670
amount: Balance,
@@ -2690,20 +2686,11 @@ async def get_unstake_fee(
26902686
Returns:
26912687
The calculated stake fee as a Balance object
26922688
"""
2693-
result = await self.query_runtime_api(
2694-
runtime_api="StakeInfoRuntimeApi",
2695-
method="get_stake_fee",
2696-
params=[
2697-
None,
2698-
coldkey_ss58,
2699-
(hotkey_ss58, netuid),
2700-
coldkey_ss58,
2701-
amount.rao,
2702-
],
2703-
block=block,
2689+
return await self.get_stake_operations_fee(
2690+
amount=amount, netuid=netuid, block=block
27042691
)
2705-
return Balance.from_rao(result)
27062692

2693+
# TODO: remove unused parameters in SDK.v10
27072694
async def get_stake_movement_fee(
27082695
self,
27092696
amount: Balance,
@@ -2731,19 +2718,9 @@ async def get_stake_movement_fee(
27312718
Returns:
27322719
The calculated stake fee as a Balance object
27332720
"""
2734-
result = await self.query_runtime_api(
2735-
runtime_api="StakeInfoRuntimeApi",
2736-
method="get_stake_fee",
2737-
params=[
2738-
(origin_hotkey_ss58, origin_netuid),
2739-
origin_coldkey_ss58,
2740-
(destination_hotkey_ss58, destination_netuid),
2741-
destination_coldkey_ss58,
2742-
amount.rao,
2743-
],
2744-
block=block,
2721+
return await self.get_stake_operations_fee(
2722+
amount=amount, netuid=origin_netuid, block=block
27452723
)
2746-
return Balance.from_rao(result)
27472724

27482725
async def get_stake_for_coldkey_and_hotkey(
27492726
self,
@@ -2864,6 +2841,39 @@ async def get_stake_for_hotkey(
28642841

28652842
get_hotkey_stake = get_stake_for_hotkey
28662843

2844+
async def get_stake_operations_fee(
2845+
self,
2846+
amount: Balance,
2847+
netuid: int,
2848+
block: Optional[int] = None,
2849+
block_hash: Optional[str] = None,
2850+
reuse_block: bool = False,
2851+
):
2852+
"""Returns fee for any stake operation in specified subnet.
2853+
2854+
Args:
2855+
amount: Amount of stake to add in Alpha/TAO.
2856+
netuid: Netuid of subnet.
2857+
block: The block number at which to query the stake information. Do not specify if also specifying
2858+
block_hash or reuse_block.
2859+
block_hash: The hash of the blockchain block number for the query. Do not specify if also specifying block
2860+
or reuse_block.
2861+
reuse_block: Whether to reuse for this query the last-used block. Do not specify if also specifying block
2862+
or block_hash.
2863+
Returns:
2864+
The calculated stake fee as a Balance object.
2865+
"""
2866+
block_hash = await self.determine_block_hash(
2867+
block=block, block_hash=block_hash, reuse_block=reuse_block
2868+
)
2869+
result = await self.substrate.query(
2870+
module="Swap",
2871+
storage_function="FeeRate",
2872+
params=[netuid],
2873+
block_hash=block_hash,
2874+
)
2875+
return amount * (result.value / U16_MAX)
2876+
28672877
async def get_subnet_burn_cost(
28682878
self,
28692879
block: Optional[int] = None,

bittensor/core/subtensor.py

Lines changed: 37 additions & 39 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
@@ -1756,6 +1760,7 @@ def get_stake(
17561760

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

1763+
# TODO: remove unused parameters in SDK.v10
17591764
def get_stake_add_fee(
17601765
self,
17611766
amount: Balance,
@@ -1777,19 +1782,7 @@ def get_stake_add_fee(
17771782
Returns:
17781783
The calculated stake fee as a Balance object
17791784
"""
1780-
result = self.query_runtime_api(
1781-
runtime_api="StakeInfoRuntimeApi",
1782-
method="get_stake_fee",
1783-
params=[
1784-
None,
1785-
coldkey_ss58,
1786-
(hotkey_ss58, netuid),
1787-
coldkey_ss58,
1788-
amount.rao,
1789-
],
1790-
block=block,
1791-
)
1792-
return Balance.from_rao(result)
1785+
return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block)
17931786

17941787
def get_subnet_info(
17951788
self, netuid: int, block: Optional[int] = None
@@ -1882,6 +1875,7 @@ def get_subnet_prices(
18821875
prices.update({0: Balance.from_tao(1)})
18831876
return prices
18841877

1878+
# TODO: remove unused parameters in SDK.v10
18851879
def get_unstake_fee(
18861880
self,
18871881
amount: Balance,
@@ -1903,20 +1897,9 @@ def get_unstake_fee(
19031897
Returns:
19041898
The calculated stake fee as a Balance object
19051899
"""
1906-
result = self.query_runtime_api(
1907-
runtime_api="StakeInfoRuntimeApi",
1908-
method="get_stake_fee",
1909-
params=[
1910-
(hotkey_ss58, netuid),
1911-
coldkey_ss58,
1912-
None,
1913-
coldkey_ss58,
1914-
amount.rao,
1915-
],
1916-
block=block,
1917-
)
1918-
return Balance.from_rao(result)
1900+
return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block)
19191901

1902+
# TODO: remove unused parameters in SDK.v10
19201903
def get_stake_movement_fee(
19211904
self,
19221905
amount: Balance,
@@ -1944,19 +1927,9 @@ def get_stake_movement_fee(
19441927
Returns:
19451928
The calculated stake fee as a Balance object
19461929
"""
1947-
result = self.query_runtime_api(
1948-
runtime_api="StakeInfoRuntimeApi",
1949-
method="get_stake_fee",
1950-
params=[
1951-
(origin_hotkey_ss58, origin_netuid),
1952-
origin_coldkey_ss58,
1953-
(destination_hotkey_ss58, destination_netuid),
1954-
destination_coldkey_ss58,
1955-
amount.rao,
1956-
],
1957-
block=block,
1930+
return self.get_stake_operations_fee(
1931+
amount=amount, netuid=origin_netuid, block=block
19581932
)
1959-
return Balance.from_rao(result)
19601933

19611934
def get_stake_for_coldkey_and_hotkey(
19621935
self,
@@ -2044,6 +2017,31 @@ def get_stake_for_hotkey(
20442017

20452018
get_hotkey_stake = get_stake_for_hotkey
20462019

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

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

tests/e2e_tests/test_stake_fee.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet):
4545
hotkey_ss58=bob_wallet.hotkey.ss58_address,
4646
)
4747
assert isinstance(unstake_fee_root, Balance), "Stake fee should be a Balance object"
48-
assert unstake_fee_root == 0, "Root unstake fee should be 0."
48+
assert unstake_fee_root == Balance.from_rao(299076829), (
49+
"Root unstake fee should be 0."
50+
)
4951

5052
# Test various stake movement scenarios
5153
movement_scenarios = [

tests/unit_tests/test_async_subtensor.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3955,3 +3955,108 @@ async def test_subnet(subtensor, mocker):
39553955
{"netuid": netuid, "price": Balance.from_tao(100.0)}
39563956
)
39573957
assert result == mocked_di_from_dict.return_value
3958+
3959+
3960+
@pytest.mark.asyncio
3961+
async def test_get_stake_operations_fee(subtensor, mocker):
3962+
"""Verify that `get_stake_operations_fee` calls proper methods and returns the correct value."""
3963+
# Preps
3964+
netuid = 1
3965+
amount = Balance.from_rao(100_000_000_000) # 100 Tao
3966+
mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash")
3967+
mocked_query_map = mocker.patch.object(
3968+
subtensor.substrate, "query", return_value=mocker.Mock(value=196)
3969+
)
3970+
3971+
# Call
3972+
result = await subtensor.get_stake_operations_fee(netuid=netuid, amount=amount)
3973+
3974+
# Assert
3975+
mocked_determine_block_hash.assert_awaited_once_with(
3976+
block=None, block_hash=None, reuse_block=False
3977+
)
3978+
mocked_query_map.assert_awaited_once_with(
3979+
module="Swap",
3980+
storage_function="FeeRate",
3981+
params=[netuid],
3982+
block_hash=mocked_determine_block_hash.return_value,
3983+
)
3984+
assert result == Balance.from_rao(299076829).set_unit(netuid)
3985+
3986+
3987+
@pytest.mark.asyncio
3988+
async def test_get_stake_add_fee(subtensor, mocker):
3989+
"""Verify that `get_stake_add_fee` calls proper methods and returns the correct value."""
3990+
# Preps
3991+
netuid = mocker.Mock()
3992+
amount = mocker.Mock()
3993+
mocked_get_stake_operations_fee = mocker.patch.object(
3994+
subtensor, "get_stake_operations_fee"
3995+
)
3996+
3997+
# Call
3998+
result = await subtensor.get_stake_add_fee(
3999+
amount=amount,
4000+
netuid=netuid,
4001+
coldkey_ss58=mocker.Mock(),
4002+
hotkey_ss58=mocker.Mock(),
4003+
)
4004+
4005+
# Asserts
4006+
mocked_get_stake_operations_fee.assert_awaited_once_with(
4007+
amount=amount, netuid=netuid, block=None
4008+
)
4009+
assert result == mocked_get_stake_operations_fee.return_value
4010+
4011+
4012+
@pytest.mark.asyncio
4013+
async def test_get_unstake_fee(subtensor, mocker):
4014+
"""Verify that `get_unstake_fee` calls proper methods and returns the correct value."""
4015+
# Preps
4016+
netuid = mocker.Mock()
4017+
amount = mocker.Mock()
4018+
mocked_get_stake_operations_fee = mocker.patch.object(
4019+
subtensor, "get_stake_operations_fee"
4020+
)
4021+
4022+
# Call
4023+
result = await subtensor.get_unstake_fee(
4024+
amount=amount,
4025+
netuid=netuid,
4026+
coldkey_ss58=mocker.Mock(),
4027+
hotkey_ss58=mocker.Mock(),
4028+
)
4029+
4030+
# Asserts
4031+
mocked_get_stake_operations_fee.assert_awaited_once_with(
4032+
amount=amount, netuid=netuid, block=None
4033+
)
4034+
assert result == mocked_get_stake_operations_fee.return_value
4035+
4036+
4037+
@pytest.mark.asyncio
4038+
async def test_get_stake_movement_fee(subtensor, mocker):
4039+
"""Verify that `get_stake_movement_fee` calls proper methods and returns the correct value."""
4040+
# Preps
4041+
netuid = mocker.Mock()
4042+
amount = mocker.Mock()
4043+
mocked_get_stake_operations_fee = mocker.patch.object(
4044+
subtensor, "get_stake_operations_fee"
4045+
)
4046+
4047+
# Call
4048+
result = await subtensor.get_stake_movement_fee(
4049+
amount=amount,
4050+
origin_netuid=netuid,
4051+
origin_hotkey_ss58=mocker.Mock(),
4052+
origin_coldkey_ss58=mocker.Mock(),
4053+
destination_netuid=mocker.Mock(),
4054+
destination_hotkey_ss58=mocker.Mock(),
4055+
destination_coldkey_ss58=mocker.Mock(),
4056+
)
4057+
4058+
# Asserts
4059+
mocked_get_stake_operations_fee.assert_awaited_once_with(
4060+
amount=amount, netuid=netuid, block=None
4061+
)
4062+
assert result == mocked_get_stake_operations_fee.return_value

0 commit comments

Comments
 (0)