Skip to content

Commit efbf98c

Browse files
authored
Merge pull request #2755 from opentensor/feat/dynamic-stake-prices
Feat/dynamic stake prices
2 parents 957649d + acc66f8 commit efbf98c

File tree

4 files changed

+429
-0
lines changed

4 files changed

+429
-0
lines changed

bittensor/core/async_subtensor.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,117 @@ async def get_stake(
15751575

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

1578+
async def get_stake_add_fee(
1579+
self,
1580+
amount: Balance,
1581+
netuid: int,
1582+
coldkey_ss58: str,
1583+
hotkey_ss58: str,
1584+
block: Optional[int] = None,
1585+
) -> Balance:
1586+
"""
1587+
Calculates the fee for adding new stake to a hotkey.
1588+
1589+
Args:
1590+
amount: Amount of stake to add in TAO
1591+
netuid: Netuid of subnet
1592+
coldkey_ss58: SS58 address of source coldkey
1593+
hotkey_ss58: SS58 address of destination hotkey
1594+
block: Block number at which to perform the calculation
1595+
1596+
Returns:
1597+
The calculated stake fee as a Balance object
1598+
"""
1599+
result = await self.query_runtime_api(
1600+
runtime_api="StakeInfoRuntimeApi",
1601+
method="get_stake_fee",
1602+
params=[
1603+
None,
1604+
coldkey_ss58,
1605+
(hotkey_ss58, netuid),
1606+
coldkey_ss58,
1607+
amount.rao,
1608+
],
1609+
block=block,
1610+
)
1611+
return Balance.from_rao(result)
1612+
1613+
async def get_unstake_fee(
1614+
self,
1615+
amount: Balance,
1616+
netuid: int,
1617+
coldkey_ss58: str,
1618+
hotkey_ss58: str,
1619+
block: Optional[int] = None,
1620+
) -> Balance:
1621+
"""
1622+
Calculates the fee for unstaking from a hotkey.
1623+
1624+
Args:
1625+
amount: Amount of stake to unstake in TAO
1626+
netuid: Netuid of subnet
1627+
coldkey_ss58: SS58 address of source coldkey
1628+
hotkey_ss58: SS58 address of destination hotkey
1629+
block: Block number at which to perform the calculation
1630+
1631+
Returns:
1632+
The calculated stake fee as a Balance object
1633+
"""
1634+
result = await self.query_runtime_api(
1635+
runtime_api="StakeInfoRuntimeApi",
1636+
method="get_stake_fee",
1637+
params=[
1638+
None,
1639+
coldkey_ss58,
1640+
(hotkey_ss58, netuid),
1641+
coldkey_ss58,
1642+
amount.rao,
1643+
],
1644+
block=block,
1645+
)
1646+
return Balance.from_rao(result)
1647+
1648+
async def get_stake_movement_fee(
1649+
self,
1650+
amount: Balance,
1651+
origin_netuid: int,
1652+
origin_hotkey_ss58: str,
1653+
origin_coldkey_ss58: str,
1654+
destination_netuid: int,
1655+
destination_hotkey_ss58: str,
1656+
destination_coldkey_ss58: str,
1657+
block: Optional[int] = None,
1658+
) -> Balance:
1659+
"""
1660+
Calculates the fee for moving stake between hotkeys/subnets/coldkeys.
1661+
1662+
Args:
1663+
amount: Amount of stake to move in TAO
1664+
origin_netuid: Netuid of source subnet
1665+
origin_hotkey_ss58: SS58 address of source hotkey
1666+
origin_coldkey_ss58: SS58 address of source coldkey
1667+
destination_netuid: Netuid of destination subnet
1668+
destination_hotkey_ss58: SS58 address of destination hotkey
1669+
destination_coldkey_ss58: SS58 address of destination coldkey
1670+
block: Block number at which to perform the calculation
1671+
1672+
Returns:
1673+
The calculated stake fee as a Balance object
1674+
"""
1675+
result = await self.query_runtime_api(
1676+
runtime_api="StakeInfoRuntimeApi",
1677+
method="get_stake_fee",
1678+
params=[
1679+
(origin_hotkey_ss58, origin_netuid),
1680+
origin_coldkey_ss58,
1681+
(destination_hotkey_ss58, destination_netuid),
1682+
destination_coldkey_ss58,
1683+
amount.rao,
1684+
],
1685+
block=block,
1686+
)
1687+
return Balance.from_rao(result)
1688+
15781689
async def get_stake_for_coldkey_and_hotkey(
15791690
self,
15801691
coldkey_ss58: str,

bittensor/core/subtensor.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,117 @@ def get_stake(
12101210

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

1213+
def get_stake_add_fee(
1214+
self,
1215+
amount: Balance,
1216+
netuid: int,
1217+
coldkey_ss58: str,
1218+
hotkey_ss58: str,
1219+
block: Optional[int] = None,
1220+
) -> Balance:
1221+
"""
1222+
Calculates the fee for adding new stake to a hotkey.
1223+
1224+
Args:
1225+
amount: Amount of stake to add in TAO
1226+
netuid: Netuid of subnet
1227+
coldkey_ss58: SS58 address of coldkey
1228+
hotkey_ss58: SS58 address of hotkey
1229+
block: Block number at which to perform the calculation
1230+
1231+
Returns:
1232+
The calculated stake fee as a Balance object
1233+
"""
1234+
result = self.query_runtime_api(
1235+
runtime_api="StakeInfoRuntimeApi",
1236+
method="get_stake_fee",
1237+
params=[
1238+
None,
1239+
coldkey_ss58,
1240+
(hotkey_ss58, netuid),
1241+
coldkey_ss58,
1242+
amount.rao,
1243+
],
1244+
block=block,
1245+
)
1246+
return Balance.from_rao(result)
1247+
1248+
def get_unstake_fee(
1249+
self,
1250+
amount: Balance,
1251+
netuid: int,
1252+
coldkey_ss58: str,
1253+
hotkey_ss58: str,
1254+
block: Optional[int] = None,
1255+
) -> Balance:
1256+
"""
1257+
Calculates the fee for unstaking from a hotkey.
1258+
1259+
Args:
1260+
amount: Amount of stake to unstake in TAO
1261+
netuid: Netuid of subnet
1262+
coldkey_ss58: SS58 address of coldkey
1263+
hotkey_ss58: SS58 address of hotkey
1264+
block: Block number at which to perform the calculation
1265+
1266+
Returns:
1267+
The calculated stake fee as a Balance object
1268+
"""
1269+
result = self.query_runtime_api(
1270+
runtime_api="StakeInfoRuntimeApi",
1271+
method="get_stake_fee",
1272+
params=[
1273+
(hotkey_ss58, netuid),
1274+
coldkey_ss58,
1275+
None,
1276+
coldkey_ss58,
1277+
amount.rao,
1278+
],
1279+
block=block,
1280+
)
1281+
return Balance.from_rao(result)
1282+
1283+
def get_stake_movement_fee(
1284+
self,
1285+
amount: Balance,
1286+
origin_netuid: int,
1287+
origin_hotkey_ss58: str,
1288+
origin_coldkey_ss58: str,
1289+
destination_netuid: int,
1290+
destination_hotkey_ss58: str,
1291+
destination_coldkey_ss58: str,
1292+
block: Optional[int] = None,
1293+
) -> Balance:
1294+
"""
1295+
Calculates the fee for moving stake between hotkeys/subnets/coldkeys.
1296+
1297+
Args:
1298+
amount: Amount of stake to move in TAO
1299+
origin_netuid: Netuid of origin subnet
1300+
origin_hotkey_ss58: SS58 address of origin hotkey
1301+
origin_coldkey_ss58: SS58 address of origin coldkey
1302+
destination_netuid: Netuid of destination subnet
1303+
destination_hotkey_ss58: SS58 address of destination hotkey
1304+
destination_coldkey_ss58: SS58 address of destination coldkey
1305+
block: Block number at which to perform the calculation
1306+
1307+
Returns:
1308+
The calculated stake fee as a Balance object
1309+
"""
1310+
result = self.query_runtime_api(
1311+
runtime_api="StakeInfoRuntimeApi",
1312+
method="get_stake_fee",
1313+
params=[
1314+
(origin_hotkey_ss58, origin_netuid),
1315+
origin_coldkey_ss58,
1316+
(destination_hotkey_ss58, destination_netuid),
1317+
destination_coldkey_ss58,
1318+
amount.rao,
1319+
],
1320+
block=block,
1321+
)
1322+
return Balance.from_rao(result)
1323+
12131324
def get_stake_for_coldkey_and_hotkey(
12141325
self,
12151326
coldkey_ss58: str,

tests/e2e_tests/test_stake_fee.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import pytest
2+
from bittensor import Balance
3+
4+
5+
@pytest.mark.parametrize("local_chain", [False], indirect=True)
6+
@pytest.mark.asyncio
7+
async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet):
8+
"""
9+
Tests the stake fee calculation mechanism for various staking operations
10+
11+
Steps:
12+
1. Register a subnet through Alice
13+
2. Test stake fees for:
14+
- Adding new stake
15+
- Removing stake
16+
- Moving stake between hotkeys/subnets/coldkeys
17+
"""
18+
19+
netuid = 2
20+
root_netuid = 0
21+
stake_amount = Balance.from_tao(100) # 100 TAO
22+
min_stake_fee = Balance.from_rao(50_000)
23+
24+
# Register subnet as Alice
25+
assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet"
26+
assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully"
27+
28+
# Test add_stake fee
29+
stake_fee_0 = subtensor.get_stake_add_fee(
30+
amount=stake_amount,
31+
netuid=netuid,
32+
coldkey_ss58=alice_wallet.coldkeypub.ss58_address,
33+
hotkey_ss58=alice_wallet.hotkey.ss58_address,
34+
)
35+
assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object"
36+
assert (
37+
stake_fee_0 >= min_stake_fee
38+
), "Stake fee should be greater than the minimum stake fee"
39+
40+
# Test unstake fee
41+
stake_fee_1 = subtensor.get_unstake_fee(
42+
amount=stake_amount,
43+
netuid=root_netuid,
44+
coldkey_ss58=alice_wallet.coldkeypub.ss58_address,
45+
hotkey_ss58=bob_wallet.hotkey.ss58_address,
46+
)
47+
assert isinstance(stake_fee_1, Balance), "Stake fee should be a Balance object"
48+
assert (
49+
stake_fee_1 >= min_stake_fee
50+
), "Stake fee should be greater than the minimum stake fee"
51+
52+
# Test various stake movement scenarios
53+
movement_scenarios = [
54+
# Move from root to non-root
55+
{
56+
"origin_netuid": root_netuid,
57+
"origin_hotkey": alice_wallet.hotkey.ss58_address,
58+
"origin_coldkey": alice_wallet.coldkeypub.ss58_address,
59+
"dest_netuid": netuid,
60+
"dest_hotkey": alice_wallet.hotkey.ss58_address,
61+
"dest_coldkey": alice_wallet.coldkeypub.ss58_address,
62+
},
63+
# Move between hotkeys on root
64+
{
65+
"origin_netuid": root_netuid,
66+
"origin_hotkey": alice_wallet.hotkey.ss58_address,
67+
"origin_coldkey": alice_wallet.coldkeypub.ss58_address,
68+
"dest_netuid": root_netuid,
69+
"dest_hotkey": bob_wallet.hotkey.ss58_address,
70+
"dest_coldkey": alice_wallet.coldkeypub.ss58_address,
71+
},
72+
# Move between coldkeys
73+
{
74+
"origin_netuid": root_netuid,
75+
"origin_hotkey": bob_wallet.hotkey.ss58_address,
76+
"origin_coldkey": alice_wallet.coldkeypub.ss58_address,
77+
"dest_netuid": root_netuid,
78+
"dest_hotkey": bob_wallet.hotkey.ss58_address,
79+
"dest_coldkey": bob_wallet.coldkeypub.ss58_address,
80+
},
81+
]
82+
83+
for scenario in movement_scenarios:
84+
stake_fee = subtensor.get_stake_movement_fee(
85+
amount=stake_amount,
86+
origin_netuid=scenario["origin_netuid"],
87+
origin_hotkey_ss58=scenario["origin_hotkey"],
88+
origin_coldkey_ss58=scenario["origin_coldkey"],
89+
destination_netuid=scenario["dest_netuid"],
90+
destination_hotkey_ss58=scenario["dest_hotkey"],
91+
destination_coldkey_ss58=scenario["dest_coldkey"],
92+
)
93+
assert isinstance(stake_fee, Balance), "Stake fee should be a Balance object"
94+
assert (
95+
stake_fee >= min_stake_fee
96+
), "Stake fee should be greater than the minimum stake fee"
97+
98+
# Test cross-subnet movement
99+
netuid2 = 3
100+
assert subtensor.register_subnet(
101+
alice_wallet
102+
), "Unable to register the second subnet"
103+
assert subtensor.subnet_exists(netuid2), "Second subnet wasn't created successfully"
104+
105+
stake_fee = subtensor.get_stake_movement_fee(
106+
amount=stake_amount,
107+
origin_netuid=netuid,
108+
origin_hotkey_ss58=bob_wallet.hotkey.ss58_address,
109+
origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address,
110+
destination_netuid=netuid2,
111+
destination_hotkey_ss58=bob_wallet.hotkey.ss58_address,
112+
destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address,
113+
)
114+
assert isinstance(stake_fee, Balance), "Stake fee should be a Balance object"
115+
assert (
116+
stake_fee >= min_stake_fee
117+
), "Stake fee should be greater than the minimum stake fee"

0 commit comments

Comments
 (0)