diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index e4de4da53..bbbbb3151 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -683,6 +683,9 @@ class RootSudoOnly(Enum): "bonds_reset_enabled": ("sudo_set_bonds_reset_enabled", RootSudoOnly.FALSE), "transfers_enabled": ("sudo_set_toggle_transfer", RootSudoOnly.FALSE), "min_allowed_uids": ("sudo_set_min_allowed_uids", RootSudoOnly.TRUE), + "sn_owner_hotkey": ("sudo_set_sn_owner_hotkey", RootSudoOnly.FALSE), + "subnet_owner_hotkey": ("sudo_set_sn_owner_hotkey", RootSudoOnly.FALSE), + "recycle_or_burn": ("sudo_set_recycle_or_burn", RootSudoOnly.FALSE), # Note: These are displayed but not directly settable via HYPERPARAMS # They are derived or set via other mechanisms "alpha_high": ("", RootSudoOnly.FALSE), # Derived from alpha_values @@ -895,6 +898,24 @@ class RootSudoOnly(Enum): "owner_settable": False, "docs_link": "docs.learnbittensor.org/subnets/subnet-hyperparameters#minalloweduids", }, + "sn_owner_hotkey": { + "description": "Set the subnet owner hotkey.", + "side_effects": "Changes which hotkey is authorized as subnet owner for the given subnet.", + "owner_settable": True, + "docs_link": "docs.learnbittensor.org/subnets/subnet-hyperparameters", + }, + "subnet_owner_hotkey": { + "description": "Alias for sn_owner_hotkey; sets the subnet owner hotkey.", + "side_effects": "Same as sn_owner_hotkey.", + "owner_settable": True, + "docs_link": "docs.learnbittensor.org/subnets/subnet-hyperparameters", + }, + "recycle_or_burn": { + "description": "Set whether subnet TAO is recycled or burned.", + "side_effects": "Controls whether unstaked TAO is recycled back into the subnet or burned.", + "owner_settable": True, + "docs_link": "docs.learnbittensor.org/subnets/subnet-hyperparameters", + }, # Additional hyperparameters that appear in chain data but aren't directly settable via HYPERPARAMS "alpha_high": { "description": "High bound of the alpha range for stake calculations.", diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py index a793ac0c0..fbd4130db 100644 --- a/tests/unit_tests/test_cli.py +++ b/tests/unit_tests/test_cli.py @@ -4,6 +4,7 @@ from async_substrate_interface import AsyncSubstrateInterface from bittensor_cli.cli import parse_mnemonic, CLIManager +from bittensor_cli.src import HYPERPARAMS, HYPERPARAMS_METADATA, RootSudoOnly from bittensor_cli.src.bittensor.extrinsics.root import ( get_current_weights_for_uid, set_root_weights_extrinsic, @@ -765,3 +766,37 @@ async def test_set_root_weights_skips_current_weights_without_prompt(): ) mock_get_current.assert_not_called() + + +# HYPERPARAMS / HYPERPARAMS_METADATA (issue #826) +NEW_HYPERPARAMS_826 = {"sn_owner_hotkey", "subnet_owner_hotkey", "recycle_or_burn"} + + +def test_new_hyperparams_in_hyperparams(): + for key in NEW_HYPERPARAMS_826: + assert key in HYPERPARAMS, f"{key} should be in HYPERPARAMS" + extrinsic, root_only = HYPERPARAMS[key] + assert extrinsic, f"{key} must have non-empty extrinsic name" + assert root_only is RootSudoOnly.FALSE + + +def test_subnet_owner_hotkey_alias_maps_to_same_extrinsic(): + ext_sn, _ = HYPERPARAMS["sn_owner_hotkey"] + ext_subnet, _ = HYPERPARAMS["subnet_owner_hotkey"] + assert ext_sn == ext_subnet == "sudo_set_sn_owner_hotkey" + + +def test_new_hyperparams_have_metadata(): + required = {"description", "side_effects", "owner_settable", "docs_link"} + for key in NEW_HYPERPARAMS_826: + assert key in HYPERPARAMS_METADATA, f"{key} should be in HYPERPARAMS_METADATA" + meta = HYPERPARAMS_METADATA[key] + for field in required: + assert field in meta, f"{key} metadata missing '{field}'" + assert isinstance(meta["description"], str) + assert isinstance(meta["owner_settable"], bool) + + +def test_new_hyperparams_owner_settable_true(): + for key in NEW_HYPERPARAMS_826: + assert HYPERPARAMS_METADATA[key]["owner_settable"] is True diff --git a/tests/unit_tests/test_hyperparams.py b/tests/unit_tests/test_hyperparams.py new file mode 100644 index 000000000..8f6b42647 --- /dev/null +++ b/tests/unit_tests/test_hyperparams.py @@ -0,0 +1,40 @@ +"""Unit tests for HYPERPARAMS and HYPERPARAMS_METADATA (issue #826).""" + +from bittensor_cli.src import HYPERPARAMS, HYPERPARAMS_METADATA, RootSudoOnly + + +NEW_HYPERPARAMS_826 = { + "sn_owner_hotkey", + "subnet_owner_hotkey", + "recycle_or_burn", +} + + +def test_new_hyperparams_in_hyperparams(): + for key in NEW_HYPERPARAMS_826: + assert key in HYPERPARAMS, f"{key} should be in HYPERPARAMS" + extrinsic, root_only = HYPERPARAMS[key] + assert extrinsic, f"{key} must have non-empty extrinsic name" + assert root_only is RootSudoOnly.FALSE + + +def test_subnet_owner_hotkey_alias_maps_to_same_extrinsic(): + ext_sn, _ = HYPERPARAMS["sn_owner_hotkey"] + ext_subnet, _ = HYPERPARAMS["subnet_owner_hotkey"] + assert ext_sn == ext_subnet == "sudo_set_sn_owner_hotkey" + + +def test_new_hyperparams_have_metadata(): + required = {"description", "side_effects", "owner_settable", "docs_link"} + for key in NEW_HYPERPARAMS_826: + assert key in HYPERPARAMS_METADATA, f"{key} should be in HYPERPARAMS_METADATA" + meta = HYPERPARAMS_METADATA[key] + for field in required: + assert field in meta, f"{key} metadata missing '{field}'" + assert isinstance(meta["description"], str) + assert isinstance(meta["owner_settable"], bool) + + +def test_new_hyperparams_owner_settable_true(): + for key in NEW_HYPERPARAMS_826: + assert HYPERPARAMS_METADATA[key]["owner_settable"] is True