diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e4c4a1c547..8e357aa141 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -75,6 +75,7 @@ u16_normalized_float, _decode_hex_identity_dict, Certificate, + u64_normalized_float, ) from bittensor.utils.balance import ( Balance, @@ -891,7 +892,7 @@ async def get_children( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> tuple[bool, list, str]: + ) -> tuple[bool, list[tuple[float, str]], str]: """ This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys storage function to get the children and formats them before returning as a tuple. @@ -921,8 +922,8 @@ async def get_children( for proportion, child in children.value: # Convert U64 to int formatted_child = decode_account_id(child[0]) - int_proportion = int(proportion) - formatted_children.append((int_proportion, formatted_child)) + normalized_proportion = u64_normalized_float(proportion) + formatted_children.append((normalized_proportion, formatted_child)) return True, formatted_children, "" else: return True, [], "" @@ -1665,6 +1666,40 @@ async def get_stake_for_coldkey( get_stake_info_for_coldkey = get_stake_for_coldkey + async def get_stake_for_hotkey( + self, + hotkey_ss58: str, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Balance: + """ + Retrieves the stake information for a given hotkey. + + Args: + hotkey_ss58: The SS58 address of the hotkey. + netuid: The subnet ID to query for. + block: The block number at which to query the stake information. Do not specify if also specifying + block_hash or reuse_block + block_hash: The hash of the blockchain block number for the query. Do not specify if also specifying block + or reuse_block + reuse_block: Whether to reuse for this query the last-used block. Do not specify if also specifying block + or block_hash. + """ + hotkey_alpha_query = await self.query_subtensor( + name="TotalHotkeyAlpha", + params=[hotkey_ss58, netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + balance = Balance.from_rao(hotkey_alpha_query.value) + balance.set_unit(netuid=netuid) + return balance + + get_hotkey_stake = get_stake_for_hotkey + async def get_subnet_burn_cost( self, block: Optional[int] = None, @@ -2683,6 +2718,9 @@ async def sign_and_send_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, sign_with: str = "coldkey", + use_nonce: bool = False, + period: Optional[int] = None, + nonce_key: str = "hotkey", ) -> tuple[bool, str]: """ Helper method to sign and submit an extrinsic call to chain. @@ -2697,14 +2735,26 @@ async def sign_and_send_extrinsic( Returns: (success, error message) """ - if sign_with not in ("coldkey", "hotkey", "coldkeypub"): + possible_keys = ("coldkey", "hotkey", "coldkeypub") + if sign_with not in possible_keys: raise AttributeError( f"'sign_with' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{sign_with}'" ) + signing_keypair = getattr(wallet, sign_with) + extrinsic_data = {"call": call, "keypair": signing_keypair} + if use_nonce: + if nonce_key not in possible_keys: + raise AttributeError( + f"'nonce_key' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{nonce_key}'" + ) + next_nonce = await self.substrate.get_account_next_index( + getattr(wallet, nonce_key).ss58_address + ) + extrinsic_data["nonce"] = next_nonce + if period is not None: + extrinsic_data["era"] = {"period": period} - extrinsic = await self.substrate.create_signed_extrinsic( - call=call, keypair=getattr(wallet, sign_with) - ) + extrinsic = await self.substrate.create_signed_extrinsic(**extrinsic_data) try: response = await self.substrate.submit_extrinsic( extrinsic, diff --git a/bittensor/core/chain_data/dynamic_info.py b/bittensor/core/chain_data/dynamic_info.py index cf55ace329..9f2739560b 100644 --- a/bittensor/core/chain_data/dynamic_info.py +++ b/bittensor/core/chain_data/dynamic_info.py @@ -10,7 +10,7 @@ from bittensor.core.chain_data.utils import decode_account_id from bittensor.core.chain_data.subnet_identity import SubnetIdentity -from bittensor.utils.balance import Balance +from bittensor.utils.balance import Balance, fixed_to_float @dataclass @@ -38,6 +38,7 @@ class DynamicInfo(InfoBase): network_registered_at: int subnet_volume: Balance subnet_identity: Optional[SubnetIdentity] + moving_price: float @classmethod def _from_dict(cls, decoded: dict) -> "DynamicInfo": @@ -120,6 +121,7 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo": network_registered_at=int(decoded["network_registered_at"]), subnet_identity=subnet_identity, subnet_volume=subnet_volume, + moving_price=fixed_to_float(decoded["moving_price"]), ) def tao_to_alpha(self, tao: Union[Balance, float, int]) -> Balance: diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index fc7205cadb..944219fcbb 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -112,7 +112,13 @@ async def add_stake_extrinsic( }, ) staking_response, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -296,7 +302,13 @@ async def add_stake_multiple_extrinsic( }, ) staking_response, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 077e6bfb14..01f41297b6 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -98,7 +98,13 @@ async def unstake_extrinsic( }, ) staking_response, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, ) if staking_response is True: # If we successfully unstaked. @@ -261,8 +267,15 @@ async def unstake_multiple_extrinsic( "netuid": netuid, }, ) + staking_response, err_msg = await subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index cb9fe16798..05fb283e9e 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -14,42 +14,6 @@ from bittensor_wallet import Wallet from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.utils.registration import torch - from scalecodec.types import GenericCall - - -async def sign_and_send_with_nonce( - subtensor: "AsyncSubtensor", - call: "GenericCall", - wallet: "Wallet", - wait_for_inclusion: bool, - wait_for_finalization: bool, - period: Optional[int] = None, -): - """ - Signs an extrinsic call with the wallet hotkey, adding an optional era for period - """ - next_nonce = await subtensor.substrate.get_account_next_index( - wallet.hotkey.ss58_address - ) - - extrinsic_data = {"call": call, "keypair": wallet.hotkey, "nonce": next_nonce} - if period is not None: - extrinsic_data["era"] = {"period": period} - - extrinsic = await subtensor.substrate.create_signed_extrinsic(**extrinsic_data) - response = await subtensor.substrate.submit_extrinsic( - extrinsic=extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - if not wait_for_finalization and not wait_for_inclusion: - return True, None - - if await response.is_success: - return True, None - - return False, format_error_message(await response.error_message) async def _do_commit_weights( @@ -87,8 +51,14 @@ async def _do_commit_weights( "commit_hash": commit_hash, }, ) - return await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + return await subtensor.sign_and_send_extrinsic( + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", ) @@ -184,8 +154,14 @@ async def _do_reveal_weights( "version_key": version_key, }, ) - return await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + return await subtensor.sign_and_send_extrinsic( + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + sign_with="hotkey", + nonce_key="hotkey", + use_nonce=True, ) @@ -290,8 +266,15 @@ async def _do_set_weights( "version_key": version_key, }, ) - return await sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization, period + return await subtensor.sign_and_send_extrinsic( + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + period=period, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", ) diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index 4a2c9b9f04..06e3295eb5 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -10,30 +10,6 @@ from bittensor.core.subtensor import Subtensor -def sign_and_send_with_nonce( - subtensor: "Subtensor", call, wallet, wait_for_inclusion, wait_for_finalization -): - next_nonce = subtensor.substrate.get_account_next_index(wallet.hotkey.ss58_address) - extrinsic = subtensor.substrate.create_signed_extrinsic( - call=call, - keypair=wallet.hotkey, - nonce=next_nonce, - ) - response = subtensor.substrate.submit_extrinsic( - extrinsic=extrinsic, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - if not wait_for_finalization and not wait_for_inclusion: - return True, None - - if response.is_success: - return True, None - - return False, format_error_message(response.error_message) - - def _do_commit_weights( subtensor: "Subtensor", wallet: "Wallet", @@ -68,9 +44,14 @@ def _do_commit_weights( "commit_hash": commit_hash, }, ) - - return sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + return subtensor.sign_and_send_extrinsic( + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + sign_with="hotkey", + nonce_key="hotkey", ) @@ -164,8 +145,14 @@ def _do_reveal_weights( "version_key": version_key, }, ) - return sign_and_send_with_nonce( - subtensor, call, wallet, wait_for_inclusion, wait_for_finalization + return subtensor.sign_and_send_extrinsic( + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + sign_with="hotkey", + nonce_key="hotkey", ) diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 5ca7b250a9..a481978155 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -107,7 +107,13 @@ def add_stake_extrinsic( }, ) staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + sign_with="coldkey", + nonce_key="coldkeypub", ) if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -280,7 +286,13 @@ def add_stake_multiple_extrinsic( }, ) staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + use_nonce=True, + nonce_key="coldkeypub", + sign_with="coldkey", ) if staking_response is True: # If we successfully staked. diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 10f286053e..51be15cce1 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -93,7 +93,13 @@ def unstake_extrinsic( }, ) staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, ) if staking_response is True: # If we successfully unstaked. @@ -248,7 +254,13 @@ def unstake_multiple_extrinsic( }, ) staking_response, err_msg = subtensor.sign_and_send_extrinsic( - call, wallet, wait_for_inclusion, wait_for_finalization + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 15ce88b691..03c32a6072 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -6,6 +6,7 @@ import requests import scalecodec from async_substrate_interface.errors import SubstrateRequestException +from async_substrate_interface.types import ScaleObj from async_substrate_interface.sync_substrate import SubstrateInterface from async_substrate_interface.utils import json from numpy.typing import NDArray @@ -77,6 +78,7 @@ u16_normalized_float, _decode_hex_identity_dict, Certificate, + u64_normalized_float, ) from bittensor.utils.balance import ( Balance, @@ -91,7 +93,6 @@ if TYPE_CHECKING: from bittensor_wallet import Wallet from async_substrate_interface.sync_substrate import QueryMapResult - from async_substrate_interface.types import ScaleObj from scalecodec.types import GenericCall @@ -678,7 +679,7 @@ def get_hyperparameter( def get_children( self, hotkey: str, netuid: int, block: Optional[int] = None - ) -> tuple[bool, list, str]: + ) -> tuple[bool, list[tuple[float, str]], str]: """ This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys storage function to get the children and formats them before returning as a tuple. @@ -704,8 +705,8 @@ def get_children( for proportion, child in children.value: # Convert U64 to int formatted_child = decode_account_id(child[0]) - int_proportion = int(proportion) - formatted_children.append((int_proportion, formatted_child)) + normalized_proportion = u64_normalized_float(proportion) + formatted_children.append((normalized_proportion, formatted_child)) return True, formatted_children, "" else: return True, [], "" @@ -1281,6 +1282,28 @@ def get_stake_for_coldkey( get_stake_info_for_coldkey = get_stake_for_coldkey + def get_stake_for_hotkey( + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None + ) -> Balance: + """ + Retrieves the stake information for a given hotkey. + + Args: + hotkey_ss58: The SS58 address of the hotkey. + netuid: The subnet ID to query for. + block: The block number at which to query the stake information. Do not specify if also specifying + block_hash or reuse_block + """ + hotkey_alpha_query = self.query_subtensor( + name="TotalHotkeyAlpha", params=[hotkey_ss58, netuid], block=block + ) + hotkey_alpha = cast(ScaleObj, hotkey_alpha_query) + balance = Balance.from_rao(hotkey_alpha.value) + balance.set_unit(netuid=netuid) + return balance + + get_hotkey_stake = get_stake_for_hotkey + def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[Balance]: """ Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the @@ -2029,6 +2052,9 @@ def sign_and_send_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = False, sign_with: str = "coldkey", + use_nonce: bool = False, + period: Optional[int] = None, + nonce_key: str = "hotkey", ) -> tuple[bool, str]: """ Helper method to sign and submit an extrinsic call to chain. @@ -2043,14 +2069,27 @@ def sign_and_send_extrinsic( Returns: (success, error message) """ - if sign_with not in ("coldkey", "hotkey", "coldkeypub"): + possible_keys = ("coldkey", "hotkey", "coldkeypub") + if sign_with not in possible_keys: raise AttributeError( f"'sign_with' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{sign_with}'" ) - extrinsic = self.substrate.create_signed_extrinsic( - call=call, keypair=getattr(wallet, sign_with) - ) + signing_keypair = getattr(wallet, sign_with) + extrinsic_data = {"call": call, "keypair": signing_keypair} + if use_nonce: + if nonce_key not in possible_keys: + raise AttributeError( + f"'nonce_key' must be either 'coldkey', 'hotkey' or 'coldkeypub', not '{nonce_key}'" + ) + next_nonce = self.substrate.get_account_next_index( + getattr(wallet, nonce_key).ss58_address + ) + extrinsic_data["nonce"] = next_nonce + if period is not None: + extrinsic_data["era"] = {"period": period} + + extrinsic = self.substrate.create_signed_extrinsic(**extrinsic_data) try: response = self.substrate.submit_extrinsic( extrinsic, diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index 3233519c16..b654431429 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -56,7 +56,7 @@ async def fake_is_success(): # Asserts assert result is True - assert message is None + assert message is "" @pytest.mark.asyncio @@ -84,7 +84,7 @@ async def fake_is_success(): mocked_format_error_message = mocker.Mock() mocker.patch.object( - async_weights, "format_error_message", mocked_format_error_message + async_subtensor, "format_error_message", mocked_format_error_message ) mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) @@ -146,7 +146,7 @@ async def test_do_set_weights_no_waiting(subtensor, mocker): # Asserts assert result is True - assert message is None + assert message is "" @pytest.mark.asyncio @@ -316,7 +316,7 @@ async def fake_is_success(): # Asserts assert result is True - assert message is None + assert message is "" @pytest.mark.asyncio @@ -340,7 +340,7 @@ async def fake_is_success(): mocked_format_error_message = mocker.Mock(return_value="Formatted error") mocker.patch.object( - async_weights, "format_error_message", mocked_format_error_message + async_subtensor, "format_error_message", mocked_format_error_message ) mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) @@ -399,7 +399,7 @@ async def test_do_commit_weights_no_waiting(subtensor, mocker): # Asserts assert result is True - assert message is None + assert message is "" @pytest.mark.asyncio diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py index 42cc1f2311..18b9e987dd 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -33,7 +33,7 @@ def test_do_commit_weights(subtensor, mocker): mocked_format_error_message = mocker.Mock() mocker.patch( - "bittensor.core.extrinsics.commit_weights.format_error_message", + "bittensor.core.subtensor.format_error_message", mocked_format_error_message, ) @@ -63,7 +63,7 @@ def test_do_commit_weights(subtensor, mocker): assert kwargs["keypair"] == fake_wallet.hotkey subtensor.substrate.submit_extrinsic.assert_called_once_with( - extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, + subtensor.substrate.create_signed_extrinsic.return_value, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) @@ -95,7 +95,7 @@ def test_do_reveal_weights(subtensor, mocker): mocked_format_error_message = mocker.Mock() mocker.patch( - "bittensor.core.extrinsics.commit_weights.format_error_message", + "bittensor.core.subtensor.format_error_message", mocked_format_error_message, ) @@ -132,7 +132,7 @@ def test_do_reveal_weights(subtensor, mocker): ) subtensor.substrate.submit_extrinsic.assert_called_once_with( - extrinsic=subtensor.substrate.create_signed_extrinsic.return_value, + subtensor.substrate.create_signed_extrinsic.return_value, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 6d9522148d..9cdd241c89 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -1,4 +1,5 @@ from bittensor.core.extrinsics import staking +from bittensor.core.extrinsics import utils from bittensor.utils.balance import Balance @@ -48,6 +49,9 @@ def test_add_stake_extrinsic(mocker): fake_wallet, True, True, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, ) @@ -132,4 +136,7 @@ def test_add_stake_multiple_extrinsic(mocker): fake_wallet, True, True, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, ) diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 9d0ed8ec03..b93f335162 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -48,6 +48,9 @@ def test_unstake_extrinsic(mocker): fake_wallet, True, True, + sign_with="coldkey", + nonce_key="coldkeypub", + use_nonce=True, ) @@ -112,4 +115,7 @@ def test_unstake_multiple_extrinsic(mocker): fake_wallet, True, True, + sign_with="coldkey", + nonce_key="coldkeypub", + use_nonce=True, ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 10d1ba5e66..53cf43ed4b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3,6 +3,7 @@ import pytest from bittensor_wallet import Wallet +from bittensor import u64_normalized_float from bittensor.core import async_subtensor from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.core.chain_data.stake_info import StakeInfo @@ -1765,8 +1766,8 @@ async def test_get_children_success(subtensor, mocker): mocker.patch.object(async_subtensor, "decode_account_id", mocked_decode_account_id) expected_formatted_children = [ - (1000, "decoded_child_key_1"), - (2000, "decoded_child_key_2"), + (u64_normalized_float(1000), "decoded_child_key_1"), + (u64_normalized_float(2000), "decoded_child_key_2"), ] # Call