Skip to content

Commit 8d1761a

Browse files
authored
Merge pull request #395 from opentensor/release/9.2.0
Release/9.2.0
2 parents fe48607 + 67dd45f commit 8d1761a

File tree

12 files changed

+472
-83
lines changed

12 files changed

+472
-83
lines changed

.github/workflows/e2e-subtensor-tests.yml

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,48 +24,81 @@ env:
2424
VERBOSE: ${{ github.event.inputs.verbose }}
2525

2626
jobs:
27-
run-tests:
28-
runs-on: SubtensorCI
29-
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
30-
timeout-minutes: 180
31-
env:
32-
RELEASE_NAME: development
33-
RUSTV: stable
34-
RUST_BACKTRACE: full
35-
RUST_BIN_DIR: target/x86_64-unknown-linux-gnu
36-
TARGET: x86_64-unknown-linux-gnu
3727

28+
find-tests:
29+
runs-on: ubuntu-latest
30+
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
31+
outputs:
32+
test-files: ${{ steps.get-tests.outputs.test-files }}
3833
steps:
3934
- name: Check-out repository under $GITHUB_WORKSPACE
40-
uses: actions/checkout@v2
35+
uses: actions/checkout@v4
4136

42-
- name: Install dependencies
37+
- name: Find test files
38+
id: get-tests
4339
run: |
44-
sudo apt-get update &&
45-
sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler
40+
test_files=$(find tests/e2e_tests -name "test*.py" | jq -R -s -c 'split("\n") | map(select(. != ""))')
41+
echo "::set-output name=test-files::$test_files"
42+
shell: bash
43+
44+
pull-docker-image:
45+
runs-on: ubuntu-latest
46+
steps:
47+
- name: Log in to GitHub Container Registry
48+
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin
49+
50+
- name: Pull Docker Image
51+
run: docker pull ghcr.io/opentensor/subtensor-localnet:latest
52+
53+
- name: Save Docker Image to Cache
54+
run: docker save -o subtensor-localnet.tar ghcr.io/opentensor/subtensor-localnet:latest
4655

47-
- name: Install Rust ${{ env.RUSTV }}
48-
uses: actions-rs/toolchain@v1.0.6
56+
- name: Upload Docker Image as Artifact
57+
uses: actions/upload-artifact@v4
4958
with:
50-
toolchain: ${{ env.RUSTV }}
51-
components: rustfmt
52-
profile: minimal
59+
name: subtensor-localnet
60+
path: subtensor-localnet.tar
5361

54-
- name: Add wasm32-unknown-unknown target
55-
run: |
56-
rustup target add wasm32-unknown-unknown --toolchain stable-x86_64-unknown-linux-gnu
57-
rustup component add rust-src --toolchain stable-x86_64-unknown-linux-gnu
62+
run-e2e-tests:
63+
name: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}
64+
needs:
65+
- find-tests
66+
- pull-docker-image
67+
runs-on: ubuntu-latest
68+
timeout-minutes: 45
69+
strategy:
70+
fail-fast: false # Allow other matrix jobs to run even if this job fails
71+
max-parallel: 32 # Set the maximum number of parallel jobs (same as we have cores in SubtensorCI runner)
72+
matrix:
73+
os:
74+
- ubuntu-latest
75+
test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }}
76+
# python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
77+
steps:
78+
- name: Check-out repository
79+
uses: actions/checkout@v4
80+
81+
- name: Install uv
82+
uses: astral-sh/setup-uv@v4
83+
with:
84+
python-version: 3.13
5885

59-
- name: Clone subtensor repo
60-
run: git clone https://github.com/opentensor/subtensor.git
86+
- name: install dependencies
87+
run: |
88+
uv venv .venv
89+
source .venv/bin/activate
90+
uv pip install .[dev]
91+
uv pip install pytest
6192
62-
- name: Setup subtensor repo
63-
working-directory: ${{ github.workspace }}/subtensor
64-
run: git checkout testnet
93+
- name: Download Cached Docker Image
94+
uses: actions/download-artifact@v4
95+
with:
96+
name: subtensor-localnet
6597

66-
- name: Install Python dependencies
67-
run: python3 -m pip install -e . pytest
98+
- name: Load Docker Image
99+
run: docker load -i subtensor-localnet.tar
68100

69-
- name: Run all tests
101+
- name: Run tests
70102
run: |
71-
LOCALNET_SH_PATH="${{ github.workspace }}/subtensor/scripts/localnet.sh" pytest tests/e2e_tests -s
103+
source .venv/bin/activate
104+
uv run pytest ${{ matrix.test-file }} -s

CHANGELOG.md

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

3+
## 9.2.0 /2025-03-18
4+
5+
## What's Changed
6+
* Improve e2e tests' workflow by @roman-opentensor in https://github.com/opentensor/btcli/pull/393
7+
* Updates to E2E suubtensor tests to devnet ready by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/390
8+
* Allow Py 3.13 install by @thewhaleking in https://github.com/opentensor/btcli/pull/392
9+
* pip install readme by @thewhaleking in https://github.com/opentensor/btcli/pull/391
10+
* Feat/dynamic staking fee by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/389
11+
12+
**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.1.4...v9.2.0
13+
314
## 9.1.4 /2025-03-13
415

516
## What's Changed

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,20 @@ Installation steps are described below. For a full documentation on how to use `
3939

4040
## Install on macOS and Linux
4141

42-
You can install `btcli` on your local machine directly from source. **Make sure you verify your installation after you install**:
42+
You can install `btcli` on your local machine directly from source, or from from PyPI. **Make sure you verify your installation after you install**:
43+
44+
45+
### Install from PyPI
46+
47+
Run
48+
```
49+
pip install -U bittensor-cli
50+
```
51+
52+
Alternatively, if you prefer to use [uv](https://pypi.org/project/uv/):
53+
```
54+
uv pip install bittensor-cli
55+
```
4356

4457
### Install from source
4558

@@ -69,6 +82,14 @@ cd btcli
6982
pip3 install .
7083
```
7184

85+
### Also install bittensor (SDK)
86+
87+
If you prefer to install the btcli alongside the bittensor SDK, you can do this in a single command with
88+
89+
```
90+
pip install -U bittensor[cli]
91+
```
92+
7293
---
7394

7495
## Install on Windows

bittensor_cli/src/bittensor/subtensor_interface.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,3 +1435,69 @@ async def get_owned_hotkeys(
14351435
)
14361436

14371437
return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]
1438+
1439+
async def get_stake_fee(
1440+
self,
1441+
origin_hotkey_ss58: Optional[str],
1442+
origin_netuid: Optional[int],
1443+
origin_coldkey_ss58: str,
1444+
destination_hotkey_ss58: Optional[str],
1445+
destination_netuid: Optional[int],
1446+
destination_coldkey_ss58: str,
1447+
amount: int,
1448+
block_hash: Optional[str] = None,
1449+
) -> Balance:
1450+
"""
1451+
Calculates the fee for a staking operation.
1452+
1453+
:param origin_hotkey_ss58: SS58 address of source hotkey (None for new stake)
1454+
:param origin_netuid: Netuid of source subnet (None for new stake)
1455+
:param origin_coldkey_ss58: SS58 address of source coldkey
1456+
:param destination_hotkey_ss58: SS58 address of destination hotkey (None for removing stake)
1457+
:param destination_netuid: Netuid of destination subnet (None for removing stake)
1458+
:param destination_coldkey_ss58: SS58 address of destination coldkey
1459+
:param amount: Amount of stake to transfer in RAO
1460+
:param block_hash: Optional block hash at which to perform the calculation
1461+
1462+
:return: The calculated stake fee as a Balance object
1463+
1464+
When to use None:
1465+
1466+
1. Adding new stake (default fee):
1467+
- origin_hotkey_ss58 = None
1468+
- origin_netuid = None
1469+
- All other fields required
1470+
1471+
2. Removing stake (default fee):
1472+
- destination_hotkey_ss58 = None
1473+
- destination_netuid = None
1474+
- All other fields required
1475+
1476+
For all other operations, no None values - provide all parameters:
1477+
3. Moving between subnets
1478+
4. Moving between hotkeys
1479+
5. Moving between coldkeys
1480+
"""
1481+
1482+
origin = None
1483+
if origin_hotkey_ss58 is not None and origin_netuid is not None:
1484+
origin = (origin_hotkey_ss58, origin_netuid)
1485+
1486+
destination = None
1487+
if destination_hotkey_ss58 is not None and destination_netuid is not None:
1488+
destination = (destination_hotkey_ss58, destination_netuid)
1489+
1490+
result = await self.query_runtime_api(
1491+
runtime_api="StakeInfoRuntimeApi",
1492+
method="get_stake_fee",
1493+
params=[
1494+
origin,
1495+
origin_coldkey_ss58,
1496+
destination,
1497+
destination_coldkey_ss58,
1498+
amount,
1499+
],
1500+
block_hash=block_hash,
1501+
)
1502+
1503+
return Balance.from_rao(result)

bittensor_cli/src/commands/stake/add.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,24 @@ async def stake_extrinsic(
282282
return False
283283
remaining_wallet_balance -= amount_to_stake
284284

285-
# Calculate slippage
286-
received_amount, slippage_pct, slippage_pct_float, rate = (
287-
_calculate_slippage(subnet_info, amount_to_stake)
285+
stake_fee = await subtensor.get_stake_fee(
286+
origin_hotkey_ss58=None,
287+
origin_netuid=None,
288+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
289+
destination_hotkey_ss58=hotkey[1],
290+
destination_netuid=netuid,
291+
destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
292+
amount=amount_to_stake.rao,
288293
)
294+
295+
# Calculate slippage
296+
try:
297+
received_amount, slippage_pct, slippage_pct_float, rate = (
298+
_calculate_slippage(subnet_info, amount_to_stake, stake_fee)
299+
)
300+
except ValueError:
301+
return False
302+
289303
max_slippage = max(slippage_pct_float, max_slippage)
290304

291305
# Add rows for the table
@@ -296,6 +310,7 @@ async def stake_extrinsic(
296310
str(rate)
297311
+ f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
298312
str(received_amount.set_unit(netuid)), # received
313+
str(stake_fee), # fee
299314
str(slippage_pct), # slippage
300315
]
301316

@@ -531,6 +546,11 @@ def _define_stake_table(
531546
justify="center",
532547
style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
533548
)
549+
table.add_column(
550+
"Fee (τ)",
551+
justify="center",
552+
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
553+
)
534554
table.add_column(
535555
"Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
536556
)
@@ -585,29 +605,41 @@ def _print_table_and_slippage(table: Table, max_slippage: float, safe_staking: b
585605

586606

587607
def _calculate_slippage(
588-
subnet_info, amount: Balance
608+
subnet_info, amount: Balance, stake_fee: Balance
589609
) -> tuple[Balance, str, float, str]:
590610
"""Calculate slippage when adding stake.
591611
592612
Args:
593613
subnet_info: Subnet dynamic info
594614
amount: Amount being staked
615+
stake_fee: Transaction fee for the stake operation
595616
596617
Returns:
597618
tuple containing:
598-
- received_amount: Amount received after slippage
619+
- received_amount: Amount received after slippage and fees
599620
- slippage_str: Formatted slippage percentage string
600621
- slippage_float: Raw slippage percentage value
622+
- rate: Exchange rate string
601623
"""
602-
received_amount, _, slippage_pct_float = subnet_info.tao_to_alpha_with_slippage(
603-
amount
604-
)
624+
amount_after_fee = amount - stake_fee
625+
626+
if amount_after_fee < 0:
627+
print_error("You don't have enough balance to cover the stake fee.")
628+
raise ValueError()
629+
630+
received_amount, _, _ = subnet_info.tao_to_alpha_with_slippage(amount_after_fee)
631+
605632
if subnet_info.is_dynamic:
633+
ideal_amount = subnet_info.tao_to_alpha(amount)
634+
total_slippage = ideal_amount - received_amount
635+
slippage_pct_float = 100 * (total_slippage.tao / ideal_amount.tao)
606636
slippage_str = f"{slippage_pct_float:.4f} %"
607637
rate = f"{(1 / subnet_info.price.tao or 1):.4f}"
608638
else:
609-
slippage_pct_float = 0
610-
slippage_str = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]N/A[/{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]"
639+
slippage_pct_float = (
640+
100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0
641+
)
642+
slippage_str = f"{slippage_pct_float:.4f} %"
611643
rate = "1"
612644

613645
return received_amount, slippage_str, slippage_pct_float, rate

0 commit comments

Comments
 (0)