Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion sdk/python/bittensor/cli/commands/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def root_list(
app_ctx.output.table(
title,
position_columns(all_wallets),
position_rows(shown),
position_rows(shown, all_wallets),
shown_records,
)
app_ctx.output.message(
Expand Down
12 changes: 7 additions & 5 deletions sdk/python/bittensor/cli/root_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,10 @@ def print_command_hint(console: Console, argv_prefix: list[str]) -> None:
def render_validator_detail(
app_ctx: AppContext, summary: dict, yours: Optional[RootPosition]
) -> None:
if app_ctx.output.json_mode:
app_ctx.output.value(summary)
return

hotkey = summary["hotkey"]
weights = summary.get("weights") or []
holdings = summary.get("holdings") or []
Expand All @@ -491,7 +495,6 @@ def render_validator_detail(
f"weights of {hotkey}",
["netuid", "share", "weight (u16)"],
weight_rows,
weights,
)
else:
app_ctx.output.message(
Expand All @@ -513,7 +516,6 @@ def render_validator_detail(
f"fund holdings of {hotkey}",
["netuid", "holding", "realizable", "spot"],
table_rows,
summary,
)

lifetime = summary.get("lifetime_return")
Expand All @@ -524,10 +526,10 @@ def render_validator_detail(
)


def position_rows(positions: list[RootPosition]) -> list[list[str]]:
def position_rows(positions: list[RootPosition], all_wallets: bool) -> list[list[str]]:
return [
[
pos.wallet or "—",
([pos.wallet or "—"] if all_wallets else [])
+ [
pos.hotkey,
str(pos.staked),
str(pos.accrued),
Expand Down
111 changes: 111 additions & 0 deletions sdk/python/tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
import pytest
from typer.testing import CliRunner

import bittensor.cli.commands.root as root_commands
import bittensor.cli.context as cli_context
from bittensor import RpcConnectionError, RpcPolicyError, __version__, wallets
from bittensor.balance import Balance
from bittensor.cli.main import app
from bittensor.cli.root_helpers import RootPosition, position_columns, position_rows
from bittensor.client import Client
from bittensor.intents import REGISTRY
from tests.harness.fake_substrate import FakeSubstrate
Expand Down Expand Up @@ -65,6 +68,29 @@ def invoke(*args: str):
return runner.invoke(app, list(args))


def seed_root_validator_summary(fake: FakeSubstrate) -> None:
fake.seed_runtime(
"BetaBasketRuntimeApi",
"get_validator_basket_summary",
{
"hotkey": BOB,
"nav_tao": 1_250_000_000,
"spot_nav_tao": 1_500_000_000,
"deposited_tao": 1_000_000_000,
"redeemed_tao": 0,
"weights": [(1, 65535)],
"holdings": [
{
"netuid": 1,
"alpha": 2_000_000_000,
"spot_tao": 1_500_000_000,
"realizable_tao": 1_250_000_000,
}
],
},
)


class TestOffline:
"""Commands that never open a connection."""

Expand Down Expand Up @@ -219,6 +245,91 @@ def test_wallet_balance_by_address(self, fake: FakeSubstrate):
assert payload["free_tao"] == pytest.approx(2.5)


class TestRoot:
@pytest.mark.parametrize("all_wallets", [False, True])
def test_position_rows_match_columns(self, all_wallets):
position = RootPosition(
hotkey=BOB,
staked=Balance.from_tao(1),
accrued=Balance.from_tao("0.25"),
wallet=_WALLET_NAME if all_wallets else None,
)

rows = position_rows([position], all_wallets)

assert all(len(row) == len(position_columns(all_wallets)) for row in rows)

def test_list_single_coldkey_renders_human_table(self, fake: FakeSubstrate, monkeypatch):
async def root_positions(_client, _coldkey_ss58):
return [
RootPosition(
hotkey=BOB,
staked=Balance.from_tao(1),
accrued=Balance.from_tao("0.25"),
)
]

monkeypatch.setattr(root_commands, "fetch_root_positions", root_positions)

result = invoke("root", "list", "--coldkey", BOB)

assert result.exit_code == 0, result.exception
assert "root positions of" in result.output
assert "staked (τ)" in result.output
assert "τ1.250000000" in result.output

def test_list_all_wallets_renders_wallet_column(self, fake: FakeSubstrate, monkeypatch):
async def all_root_positions(_client, _coldkeys):
return [
RootPosition(
hotkey=BOB,
staked=Balance.from_tao(1),
accrued=Balance.from_tao("0.25"),
wallet=_WALLET_NAME,
coldkey=BOB,
)
]

monkeypatch.setattr(root_commands, "list_coldkeys", lambda _path: [(_WALLET_NAME, BOB)])
monkeypatch.setattr(root_commands, "fetch_all_root_positions", all_root_positions)

result = invoke("root", "list", "--all")

assert result.exit_code == 0, result.exception
assert "wallet" in result.output
assert _WALLET_NAME in result.output
assert "τ1.250000000" in result.output

def test_show_explicit_hotkey_renders_human_detail(self, fake: FakeSubstrate, monkeypatch):
async def root_positions(_client, _coldkey_ss58):
return []

monkeypatch.setattr(root_commands, "fetch_root_positions", root_positions)
seed_root_validator_summary(fake)

result = invoke("root", "show", "--hotkey", BOB, "--coldkey", BOB)

assert result.exit_code == 0, result.exception
assert "weights of" in result.output
assert "fund holdings of" in result.output
assert "fund nav: τ1.250000000" in result.output

def test_show_explicit_hotkey_json_emits_one_document(self, fake: FakeSubstrate, monkeypatch):
async def root_positions(_client, _coldkey_ss58):
return []

monkeypatch.setattr(root_commands, "fetch_root_positions", root_positions)
seed_root_validator_summary(fake)

result = invoke("--json", "root", "show", "--hotkey", BOB, "--coldkey", BOB)

assert result.exit_code == 0, result.exception
payload = json.loads(result.output)
assert payload["hotkey"] == BOB
assert payload["nav_tao"] == "τ1.250000000"
assert payload["weights"] == [{"netuid": 1, "weight": 65535, "share": 1.0}]


class TestTransactions:
def test_dry_run_renders_plan_without_submitting(self, fake: FakeSubstrate):
result = invoke(
Expand Down
Loading