diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index c2afd3e7..a82a8196 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -8,6 +8,8 @@ jobs: - uses: actions/checkout@v2 - name: Install Python dependencies uses: py-actions/py-dependency-install@v2 + - name: Install dev dependencies + run: pip install -r requirements-dev.txt - uses: tsuyoshicho/action-mypy@v3 with: github_token: ${{ secrets.github_token }} diff --git a/multiversx_sdk_cli/cli_validators.py b/multiversx_sdk_cli/cli_validators.py index 51e7f09c..eae67200 100644 --- a/multiversx_sdk_cli/cli_validators.py +++ b/multiversx_sdk_cli/cli_validators.py @@ -1,10 +1,13 @@ -from typing import Any, List +from pathlib import Path +from typing import Any -from multiversx_sdk_cli import cli_shared, utils, validators -from multiversx_sdk_cli.transactions import do_prepare_transaction +from multiversx_sdk import Address +from multiversx_sdk_cli import cli_shared, utils +from multiversx_sdk_cli.validators.core import ValidatorsController -def setup_parser(args: List[str], subparsers: Any) -> Any: + +def setup_parser(args: list[str], subparsers: Any) -> Any: parser = cli_shared.add_group_subparser( subparsers, "validator", @@ -117,7 +120,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: return subparsers -def _add_common_arguments(args: List[str], sub: Any): +def _add_common_arguments(args: list[str], sub: Any): cli_shared.add_proxy_arg(sub) cli_shared.add_wallet_args(args, sub) cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_estimate_gas=True) @@ -136,120 +139,431 @@ def _add_nodes_arg(sub: Any): def do_stake(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_stake(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + rewards_address = Address.new_from_bech32(args.reward_address) if args.reward_address else None + + controller = ValidatorsController(args.chain) + + if args.top_up: + tx = controller.create_transaction_for_topping_up( + sender=sender, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) + else: + validators_file = Path(args.validators_file) + tx = controller.create_transaction_for_staking( + sender=sender, + validators_file=validators_file, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + rewards_address=rewards_address, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) -def do_unstake(args: Any): +def validate_args(args: Any) -> None: + cli_shared.check_guardian_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unstake(args) - tx = do_prepare_transaction(args) + + +def prepare_sender(args: Any): + sender = cli_shared.prepare_account(args) + nonce = ( + int(args.nonce) + if args.nonce is not None + else cli_shared.get_current_nonce_for_address(sender.address, args.proxy) + ) + return sender, nonce + + +def prepare_guardian(args: Any): + guardian = cli_shared.load_guardian_account(args) + guardian_address = cli_shared.get_guardian_address(guardian, args) + return guardian, guardian_address + + +def prepare_relayer(args: Any): + relayer = cli_shared.load_relayer_account(args) + relayer_address = cli_shared.get_relayer_address(relayer, args) + return relayer, relayer_address + + +def do_unstake(args: Any): + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + keys = args.nodes_public_keys + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_unstaking( + sender=sender, + keys=keys, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def do_unjail(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unjail(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + keys = args.nodes_public_keys + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_unjailing( + sender=sender, + keys=keys, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def do_unbond(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unbond(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + keys = args.nodes_public_keys + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_unbonding( + sender=sender, + keys=keys, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def change_reward_address(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_change_reward_address(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + rewards_address = Address.new_from_bech32(args.reward_address) + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_changing_rewards_address( + sender=sender, + rewards_address=rewards_address, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def do_claim(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_claim(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_claiming( + sender=sender, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def do_unstake_nodes(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unstake_nodes(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + + keys = args.nodes_public_keys + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_unstaking_nodes( + sender=sender, + keys=keys, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def do_unstake_tokens(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unstake_tokens(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + value = int(args.unstake_value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_unstaking_tokens( + sender=sender, + value=value, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def do_unbond_nodes(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unbond_nodes(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + + keys = args.nodes_public_keys + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_unbonding_nodes( + sender=sender, + keys=keys, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def do_unbond_tokens(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_unbond_tokens(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + value = int(args.unbond_value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_unbonding_tokens( + sender=sender, + value=value, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def do_clean_registered_data(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_clean_registered_data(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_cleaning_registered_data( + sender=sender, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) def do_restake_unstaked_nodes(args: Any): - cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) - cli_shared.prepare_chain_id_in_args(args) - validators.prepare_args_for_restake_unstaked_nodes(args) - tx = do_prepare_transaction(args) + validate_args(args) + sender, nonce = prepare_sender(args) + guardian, guardian_address = prepare_guardian(args) + relayer, relayer_address = prepare_relayer(args) + + native_amount = int(args.value) + gas_limit = 0 if args.estimate_gas else args.gas_limit + keys = args.nodes_public_keys + + controller = ValidatorsController(args.chain) + tx = controller.create_transaction_for_restaking_unstaked_nodes( + sender=sender, + keys=keys, + native_amount=native_amount, + estimate_gas=args.estimate_gas, + gas_limit=gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/config.py b/multiversx_sdk_cli/config.py index d5e61b52..afcf82b2 100644 --- a/multiversx_sdk_cli/config.py +++ b/multiversx_sdk_cli/config.py @@ -24,6 +24,8 @@ class MetaChainSystemSCsCost: DELEGATION_OPS = 1000000 UNSTAKE_TOKENS = 5000000 UNBOND_TOKENS = 5000000 + CLEAN_REGISTERED_DATA = 5000000 + RESTAKE_UNSTAKED_NODES = 5000000 def get_dependency_resolution(key: str) -> str: diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 947788cd..43c9e7b5 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -280,71 +280,10 @@ def _prepare_args_for_factory(self, arguments: list[str]) -> list[Any]: def _hex_to_bytes(self, arg: str): argument = arg[len(HEX_PREFIX) :] argument = argument.upper() - argument = ensure_even_length(argument) + argument = self.ensure_even_length(argument) return bytes.fromhex(argument) - -def prepare_execute_transaction_data(function: str, arguments: list[Any]) -> str: - tx_data = function - - for arg in arguments: - tx_data += f"@{_prepare_argument(arg)}" - - return tx_data - - -# only used for stake operations -def _prepare_argument(argument: Any): - as_str = str(argument) - as_hex = _to_hex(as_str) - return as_hex - - -def _to_hex(arg: str): - if arg.startswith(HEX_PREFIX): - return _prepare_hexadecimal(arg) - - if arg.isnumeric(): - return _prepare_decimal(arg) - elif arg.startswith(get_address_hrp()): - addr = Address.from_bech32(arg) - return _prepare_hexadecimal(f"{HEX_PREFIX}{addr.hex()}") - elif arg.lower() == FALSE_STR_LOWER or arg.lower() == TRUE_STR_LOWER: - as_str = f"{HEX_PREFIX}01" if arg.lower() == TRUE_STR_LOWER else f"{HEX_PREFIX}00" - return _prepare_hexadecimal(as_str) - elif arg.startswith(STR_PREFIX): - as_hex = f"{HEX_PREFIX}{arg[len(STR_PREFIX):].encode('ascii').hex()}" - return _prepare_hexadecimal(as_hex) - else: - raise Exception(f"could not convert {arg} to hex") - - -def _prepare_hexadecimal(argument: str) -> str: - argument = argument[len(HEX_PREFIX) :] - argument = argument.upper() - argument = ensure_even_length(argument) - - if argument == "": - return "" - - try: - _ = int(argument, 16) - except ValueError: - raise errors.UnknownArgumentFormat(argument) - return argument - - -def _prepare_decimal(argument: str) -> str: - if not argument.isnumeric(): - raise errors.UnknownArgumentFormat(argument) - - as_number = int(argument) - as_hexstring = hex(as_number)[len(HEX_PREFIX) :] - as_hexstring = ensure_even_length(as_hexstring) - return as_hexstring.upper() - - -def ensure_even_length(string: str) -> str: - if len(string) % 2 == 1: - return "0" + string - return string + def ensure_even_length(self, string: str) -> str: + if len(string) % 2 == 1: + return "0" + string + return string diff --git a/multiversx_sdk_cli/dns.py b/multiversx_sdk_cli/dns.py index 60a9d981..44a52369 100644 --- a/multiversx_sdk_cli/dns.py +++ b/multiversx_sdk_cli/dns.py @@ -1,4 +1,4 @@ -from typing import Any, List, Protocol +from typing import Any, Protocol from Cryptodome.Hash import keccak from multiversx_sdk import ( @@ -8,10 +8,10 @@ SmartContractQueryResponse, ) -from multiversx_sdk_cli import cli_shared, utils +from multiversx_sdk_cli import cli_shared from multiversx_sdk_cli.config import get_address_hrp from multiversx_sdk_cli.constants import ADDRESS_ZERO_HEX -from multiversx_sdk_cli.transactions import do_prepare_transaction +from multiversx_sdk_cli.transactions import TransactionsController MaxNumShards = 256 ShardIdentiferLen = 2 @@ -55,22 +55,53 @@ def validate_name(name: str, shard_id: int, proxy: INetworkProvider): def register(args: Any): - args = utils.as_object(args) - cli_shared.check_guardian_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - args.receiver = dns_address_for_name(args.name).bech32() - args.data = dns_register_data(args.name) - tx = do_prepare_transaction(args) + sender = cli_shared.prepare_account(args) + + if args.nonce is None: + nonce = cli_shared.get_current_nonce_for_address(sender.address, args.proxy) + else: + nonce = int(args.nonce) + + guardian = cli_shared.load_guardian_account(args) + guardian_address = cli_shared.get_guardian_address(guardian, args) + + relayer = cli_shared.load_relayer_account(args) + relayer_address = cli_shared.get_relayer_address(relayer, args) + + native_amount = int(args.value) + + receiver = dns_address_for_name(args.name) + data = dns_register_data(args.name) + + controller = TransactionsController(args.chain) + + tx = controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=args.gas_limit, + gas_price=args.gas_price, + nonce=nonce, + version=args.version, + options=args.options, + data=data, + guardian_account=guardian, + guardian_address=guardian_address, + relayer_account=relayer, + relayer_address=relayer_address, + guardian_service_url=args.guardian_service_url, + guardian_2fa_code=args.guardian_2fa_code, + ) cli_shared.send_or_simulate(tx, args) -def compute_all_dns_addresses() -> List[Address]: - addresses: List[Address] = [] +def compute_all_dns_addresses() -> list[Address]: + addresses: list[Address] = [] for i in range(0, 256): addresses.append(compute_dns_address_for_shard_id(i)) return addresses @@ -122,15 +153,15 @@ def compute_dns_address_for_shard_id(shard_id: int) -> Address: def dns_register_data(name: str) -> str: - name_enc: bytes = str.encode(name) - return "register@{}".format(name_enc.hex()) + name_as_hex = str.encode(name).hex() + return f"register@{name_as_hex}" def _query_contract( contract_address: Address, proxy: INetworkProvider, function: str, - args: List[bytes], + args: list[bytes], ) -> SmartContractQueryResponse: query = SmartContractQuery(contract=contract_address, function=function, arguments=args) return proxy.query_contract(query) diff --git a/multiversx_sdk_cli/tests/test_cli_dns.py b/multiversx_sdk_cli/tests/test_cli_dns.py index 21da2b60..9241e393 100644 --- a/multiversx_sdk_cli/tests/test_cli_dns.py +++ b/multiversx_sdk_cli/tests/test_cli_dns.py @@ -1,11 +1,13 @@ +import json from pathlib import Path +from typing import Any from multiversx_sdk_cli.cli import main testdata_path = Path(__file__).parent / "testdata" -def test_prepare_relayed_dns_register_transaction(): +def test_prepare_relayed_dns_register_transaction(capsys: Any): alice = testdata_path / "alice.pem" user = testdata_path / "testUser.pem" @@ -29,5 +31,37 @@ def test_prepare_relayed_dns_register_transaction(): str(user), ] ) + assert not return_code - assert False if return_code else True + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqpgqf97pgqdy0tstwauxu09kszz020hp5kgqqzzsscqtww" + assert tx["value"] == "0" + assert tx["nonce"] == 0 + assert tx["gasLimit"] == 15000000 + assert tx["chainID"] == "T" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert tx["relayer"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert ( + tx["signature"] + == "25af35896b853ad13bf1adcc3e516f8d7532349b4b00e958e30a6b9c0b9c38cbe0ce712684aba91e3f91bf080f6ae89d8cdfd6d0c69701d3761346aa6c54ac0d" + ) + assert ( + tx["relayerSignature"] + == "e7b22c3f8e3cfa8f15038d3b59beabe3e4b2a0e40fdb40e57c39e762450ebe3cdf327bb66585c27e66480846b7487d5e78366959f6f09f10bb63e9b643c08f03" + ) + assert data == "register@616c6963652e656c726f6e64" + + +def get_output(capsys: Any): + tx = _read_stdout(capsys) + return json.loads(tx) + + +def _read_stdout(capsys: Any) -> str: + stdout: str = capsys.readouterr().out.strip() + return stdout diff --git a/multiversx_sdk_cli/tests/test_cli_validators.py b/multiversx_sdk_cli/tests/test_cli_validators.py index 04576f94..a6bff22a 100644 --- a/multiversx_sdk_cli/tests/test_cli_validators.py +++ b/multiversx_sdk_cli/tests/test_cli_validators.py @@ -1,23 +1,23 @@ +import json from pathlib import Path - -import pytest +from typing import Any from multiversx_sdk_cli.cli import main testdata_path = Path(__file__).parent / "testdata" testdata_out = Path(__file__).parent / "testdata-out" -proxy_url = "http://localhost:7950/network/config" alice_pem = testdata_path / "alice.pem" reward_address = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" bls_key = "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" +relayer = testdata_path / "testUser.pem" +guardian = testdata_path / "testUser2.pem" + -@pytest.mark.require_localnet -def test_stake(): - validators_json = testdata_path / "validators_ci.json" +def test_stake(capsys: Any): + validators_json = testdata_path / "validators_file.json" - # Stake with recall nonce return_code = main( [ "validator", @@ -32,15 +32,76 @@ def test_stake(): reward_address, "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=0", + ] + ) + assert return_code == 0 + + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2500000000000000000000" + assert tx["nonce"] == 0 + assert tx["gasLimit"] == 11029500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "e9dd1159bc55bde84872f0595c9e24b0b210deb8cb02a1df7d7b0e1277436d2043437ffeaff540e11200c00417cf5ce396b3154f968ad62ab4359fb05a493b0d" + ) + assert ( + data + == "stake@02@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1865870f7f69162a2dfefd33fe232a9ca984c6f22d1ee3f6a5b34a8eb8c9f7319001f29d5a2eed85c1500aca19fa4189@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d@12b309791213aac8ad9f34f0d912261e30f9ab060859e4d515e020a98b91d82a7cd334e4b504bb93d6b75347cccd6318@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba" + ) + + +def test_top_up(capsys: Any): + return_code = main( + [ + "validator", + "stake", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--top-up", + "--chain", + "localnet", + "--estimate-gas", + "--nonce=0", + "--reward-address", + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", ] ) assert return_code == 0 - # Stake with provided nonce + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2500000000000000000000" + assert tx["nonce"] == 0 + assert tx["gasLimit"] == 5057500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "ca4ebd1b9c92b0479351e9f84b0394ce15f529f4a5c056ab2dd37b923d7af81cbb7bfc8fbbea571843a797e3382795c0419f69ab357acbd9611899d39e449107" + ) + assert data == "stake" + + +def test_stake_with_relayer_and_guardian(capsys: Any): + validators_json = testdata_path / "validators_file.json" + return_code = main( [ "validator", @@ -55,18 +116,48 @@ def test_stake(): reward_address, "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", "--nonce=0", + "--options=2", + "--relayer", + "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5", + "--guardian", + "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4", + "--guardian-pem", + str(guardian), ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2500000000000000000000" + assert tx["nonce"] == 0 + assert tx["gasLimit"] == 11029500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 2 + assert tx["guardian"] == "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" + assert tx["relayer"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" + assert ( + tx["signature"] + == "9ecca226c3e5913906a5f22971a0d84bb7a8c652e309ffab4073dea8bbd88caccc2ad9e33b6929f16acdedb676ad574ad66d520abd5b398469fa0cff3fc7ca03" + ) + assert ( + tx["guardianSignature"] + == "12c1ee05d34282555d85f7786dc0e7ffbce960de88fb75ba81a237bd1f2cc175f50ee42e60b2857bf2cd49d02de12a4017f1c95f14910fcc27bc7cb16b41ce04" + ) + assert ( + data + == "stake@02@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1865870f7f69162a2dfefd33fe232a9ca984c6f22d1ee3f6a5b34a8eb8c9f7319001f29d5a2eed85c1500aca19fa4189@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d@12b309791213aac8ad9f34f0d912261e30f9ab060859e4d515e020a98b91d82a7cd334e4b504bb93d6b75347cccd6318@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba" + ) + -@pytest.mark.require_localnet -def test_stake_top_up(): - # Stake with topUp +def test_stake_top_up(capsys: Any): return_code = main( [ "validator", @@ -78,18 +169,32 @@ def test_stake_top_up(): "2711000000000000000000", "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] -@pytest.mark.require_localnet -def test_unstake(): - # Unstake + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2711000000000000000000" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5057500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "3af38033e7661311e2e180b29930271ad54c05716e13bb33bafdb89e48250db525c056a51a25faf8e5bba31f6ddc03ed48f9e0e79b5f3da5ccc9f0b0a9a83207" + ) + assert data == "stake" + + +def test_unstake(capsys: Any): return_code = main( [ "validator", @@ -100,18 +205,35 @@ def test_unstake(): bls_key, "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5350000 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "b387f3255670f17dbc4dd84bbed1f70631d2528f8f8ece7fb5c0fcc29a8b0b142583fe216c9de0086ebb69f9a50fc087ac4e5570fa2f61694df3b2cdb9389008" + ) + assert ( + data + == "unStake@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + -@pytest.mark.require_localnet -def test_unbond(): - # Unbond +def test_unbond(capsys: Any): return_code = main( [ "validator", @@ -122,18 +244,35 @@ def test_unbond(): bls_key, "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5348500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "65c01d7c0ac26169d74d56612d1eab3a00c82b2f57af6b4aebbf39aa75e0f5e973d7daabb00bfad14d2f8bf63936ca9d69ef8fb76b8ac9c8ad0d2f808936930e" + ) + assert ( + data + == "unBond@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + -@pytest.mark.require_localnet -def test_unjail(): - # Unjail +def test_unjail(capsys: Any): return_code = main( [ "validator", @@ -146,18 +285,35 @@ def test_unjail(): bls_key, "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] -@pytest.mark.require_localnet -def test_change_reward_address(): - # Change reward address + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "2500000000000000000000" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5348500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "5e682ba1e16b62971d3c6e6943ec954eb29fc31d8794d21afdd9e2c4ea5ba209a59cba8bd39c2a6ab9f80f066d558679e5b22bfca561caedbfa6a7297ad97d00" + ) + assert ( + data + == "unJail@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + + +def test_change_reward_address(capsys: Any): return_code = main( [ "validator", @@ -168,18 +324,66 @@ def test_change_reward_address(): reward_address, "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", + ] + ) + assert return_code == 0 + + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5176000 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "565e64ea28d48150e3798e20c0a0a0294374992ed05e5153e93339b3f86acdd25db308b474dd8315a544d6e375ed9e07b01c3475bdc7986fde79eae26bd5a40c" + ) + assert data == "changeRewardAddress@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba" + + +def test_claim(capsys: Any): + return_code = main( + [ + "validator", + "claim", + "--pem", + str(alice_pem), + "--chain", + "localnet", + "--estimate-gas", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5057500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "be19a2c0bf5ce1da5f72a7451bff57725161bb67a8b85e397d44570585e6b7ff40858b0d30fd9a12f06c70655b1c417389f25059e0a95dc76e488185cea68208" + ) + assert data == "claim" + -@pytest.mark.require_localnet -def test_unstake_nodes(): - # Unstake Nodes +def test_unstake_nodes(capsys: Any): return_code = main( [ "validator", @@ -190,18 +394,35 @@ def test_unstake_nodes(): bls_key, "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] -@pytest.mark.require_localnet -def test_unstake_tokens(): - # Unstake Tokens + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5357500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "983b3127490949bc29e722a40a87871e88a6eb085fffb4d2801b2a79b39ff0aa395a39dc03367edb5f3858ed7ad9bea7b0c141110717cb639c45010055254606" + ) + assert ( + data + == "unStakeNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + + +def test_unstake_tokens(capsys: Any): return_code = main( [ "validator", @@ -212,18 +433,32 @@ def test_unstake_tokens(): "11000000000000000000", "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5095000 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "ed8e401e875d70bc3a62bf966fc8a9ecda2d49a851fe216f265176be5ab43040a85df55798dc828c928079573e2aa8dc52627e87c92824d8c91fdc3f3d195e0a" + ) + assert data == "unStakeTokens@98a7d9b8314c0000" + -@pytest.mark.require_localnet -def test_unbond_nodes(): - # Unbond nodes +def test_unbond_nodes(capsys: Any): return_code = main( [ "validator", @@ -234,18 +469,35 @@ def test_unbond_nodes(): bls_key, "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5356000 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "ee261c7e7f1dc7822b31c609c570ba1b1da3e39ea68e231f2aea30ca9f70e0f61679f81739f22eee338fa9b8c14d498ca8892cde118d53b08f1440fe3737eb02" + ) + assert ( + data + == "unBondNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + -@pytest.mark.require_localnet -def test_unbond_tokens(): - # Unbond nodes +def test_unbond_tokens(capsys: Any): return_code = main( [ "validator", @@ -256,18 +508,32 @@ def test_unbond_tokens(): "20000000000000000000", "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5096500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "a7c96028a97d035c0068b9c2a4bbc4ee3b9613d81dfa4c388fd8a90e66f4e200e715e87a760c0a7d456f3b3a4dc225f760084477cb15ac690c9e1cb7c006f70d" + ) + assert data == "unBondTokens@01158e460913d00000" + -@pytest.mark.require_localnet -def test_clean_registration_data(): - # Clean registration data +def test_clean_registration_data(capsys: Any): return_code = main( [ "validator", @@ -276,18 +542,32 @@ def test_clean_registration_data(): str(alice_pem), "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] -@pytest.mark.require_localnet -def test_re_stake_unstaked_nodes(): - # Clean registration data + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5078500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "005c35ccf2bbffbc753c8971aba1edffb43dbad1db62a88a26d295445937bb7f84dfd26e31329f8622ec9b53c5be4a39f1dd8ab83189f2cd5211c1c8541d7b00" + ) + assert data == "cleanRegisteredData" + + +def test_re_stake_unstaked_nodes(capsys: Any): return_code = main( [ "validator", @@ -298,10 +578,39 @@ def test_re_stake_unstaked_nodes(): bls_key, "--chain", "localnet", - "--proxy", - "http://127.0.0.1:7950", "--estimate-gas", - "--recall-nonce", + "--nonce=7", ] ) assert return_code == 0 + + output = get_output(capsys) + tx = output["emittedTransaction"] + data = output["emittedTransactionData"] + + assert tx["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert tx["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + assert tx["value"] == "0" + assert tx["nonce"] == 7 + assert tx["gasLimit"] == 5369500 + assert tx["chainID"] == "localnet" + assert tx["version"] == 2 + assert tx["options"] == 0 + assert ( + tx["signature"] + == "5f2196b81d9a72df401655becfc31e4167d89e76235f52abf506f9d9b10375b8b699339693b3f2d12552366e38ec2722a2ed50490a2beeaee0ae819d08f1ea0e" + ) + assert ( + data + == "reStakeUnStakedNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + ) + + +def get_output(capsys: Any): + tx = _read_stdout(capsys) + return json.loads(tx) + + +def _read_stdout(capsys: Any) -> str: + stdout: str = capsys.readouterr().out.strip() + return stdout diff --git a/multiversx_sdk_cli/tests/test_cli_validators_localnet.py b/multiversx_sdk_cli/tests/test_cli_validators_localnet.py new file mode 100644 index 00000000..04576f94 --- /dev/null +++ b/multiversx_sdk_cli/tests/test_cli_validators_localnet.py @@ -0,0 +1,307 @@ +from pathlib import Path + +import pytest + +from multiversx_sdk_cli.cli import main + +testdata_path = Path(__file__).parent / "testdata" +testdata_out = Path(__file__).parent / "testdata-out" + +proxy_url = "http://localhost:7950/network/config" +alice_pem = testdata_path / "alice.pem" +reward_address = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" +bls_key = "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208" + + +@pytest.mark.require_localnet +def test_stake(): + validators_json = testdata_path / "validators_ci.json" + + # Stake with recall nonce + return_code = main( + [ + "validator", + "stake", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--validators-file", + str(validators_json), + "--reward-address", + reward_address, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + # Stake with provided nonce + return_code = main( + [ + "validator", + "stake", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--validators-file", + str(validators_json), + "--reward-address", + reward_address, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--nonce=0", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_stake_top_up(): + # Stake with topUp + return_code = main( + [ + "validator", + "stake", + "--top-up", + "--pem", + str(alice_pem), + "--value", + "2711000000000000000000", + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unstake(): + # Unstake + return_code = main( + [ + "validator", + "unstake", + "--pem", + str(alice_pem), + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unbond(): + # Unbond + return_code = main( + [ + "validator", + "unbond", + "--pem", + str(alice_pem), + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unjail(): + # Unjail + return_code = main( + [ + "validator", + "unjail", + "--pem", + str(alice_pem), + "--value", + "2500000000000000000000", + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_change_reward_address(): + # Change reward address + return_code = main( + [ + "validator", + "change-reward-address", + "--pem", + str(alice_pem), + "--reward-address", + reward_address, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unstake_nodes(): + # Unstake Nodes + return_code = main( + [ + "validator", + "unstake-nodes", + "--pem", + str(alice_pem), + "--nodes-public-key", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unstake_tokens(): + # Unstake Tokens + return_code = main( + [ + "validator", + "unstake-tokens", + "--pem", + str(alice_pem), + "--unstake-value", + "11000000000000000000", + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unbond_nodes(): + # Unbond nodes + return_code = main( + [ + "validator", + "unbond-nodes", + "--pem", + str(alice_pem), + "--nodes-public-keys", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_unbond_tokens(): + # Unbond nodes + return_code = main( + [ + "validator", + "unbond-tokens", + "--pem", + str(alice_pem), + "--unbond-value", + "20000000000000000000", + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_clean_registration_data(): + # Clean registration data + return_code = main( + [ + "validator", + "clean-registered-data", + "--pem", + str(alice_pem), + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 + + +@pytest.mark.require_localnet +def test_re_stake_unstaked_nodes(): + # Clean registration data + return_code = main( + [ + "validator", + "restake-unstaked-nodes", + "--pem", + str(alice_pem), + "--nodes-public-keys", + bls_key, + "--chain", + "localnet", + "--proxy", + "http://127.0.0.1:7950", + "--estimate-gas", + "--recall-nonce", + ] + ) + assert return_code == 0 diff --git a/multiversx_sdk_cli/tests/test_contracts.py b/multiversx_sdk_cli/tests/test_contracts.py index ef312959..d81cad3f 100644 --- a/multiversx_sdk_cli/tests/test_contracts.py +++ b/multiversx_sdk_cli/tests/test_contracts.py @@ -1,13 +1,11 @@ import logging from pathlib import Path -import pytest from Cryptodome.Hash import keccak from multiversx_sdk import Account, Address, TransactionsFactoryConfig -from multiversx_sdk_cli import errors from multiversx_sdk_cli.contract_verification import _create_request_signature -from multiversx_sdk_cli.contracts import SmartContract, _prepare_argument +from multiversx_sdk_cli.contracts import SmartContract logging.basicConfig(level=logging.INFO) @@ -19,39 +17,6 @@ def test_playground_keccak(): assert hexhash == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" -def test_prepare_argument(): - assert _prepare_argument("0x5") == "05" - assert _prepare_argument("5") == "05" - assert _prepare_argument("0x05f") == "005F" - assert _prepare_argument("0xaaa") == "0AAA" - assert _prepare_argument("str:a") == "61" - assert _prepare_argument("str:aaa") == "616161" - - assert _prepare_argument(155) == "9B" - assert _prepare_argument("155") == "9B" - - assert ( - _prepare_argument("erd1qr9av6ar4ymr05xj93jzdxyezdrp6r4hz6u0scz4dtzvv7kmlldse7zktc") - == "00CBD66BA3A93637D0D22C6426989913461D0EB716B8F860556AC4C67ADBFFDB" - ) - - assert _prepare_argument("str:TOK-123456") == "544F4B2D313233343536" - assert _prepare_argument("str:TOK-a1c2ef") == "544F4B2D613163326566" - assert _prepare_argument("str:TokenName") == "546F6B656E4E616D65" - assert _prepare_argument("str:/#%placeholder&*") == "2F2325706C616365686F6C646572262A" - - assert _prepare_argument(True) == "01" - assert _prepare_argument(False) == "00" - assert _prepare_argument("TrUe") == "01" - assert _prepare_argument("fAlSe") == "00" - - with pytest.raises(errors.UnknownArgumentFormat): - _ = _prepare_argument("0x05fq") - - assert _prepare_argument("str:") == "" - assert _prepare_argument("0x") == "" - - def test_contract_verification_create_request_signature(): account = Account.new_from_pem(file_path=testdata_folder / "walletKey.pem") contract_address = Address.from_bech32("erd1qqqqqqqqqqqqqpgqeyj9g344pqguukajpcfqz9p0rfqgyg4l396qespdck") diff --git a/multiversx_sdk_cli/tests/test_validators_core.py b/multiversx_sdk_cli/tests/test_validators_core.py deleted file mode 100644 index b382e2bb..00000000 --- a/multiversx_sdk_cli/tests/test_validators_core.py +++ /dev/null @@ -1,21 +0,0 @@ -from pathlib import Path - -from multiversx_sdk import Address - -from multiversx_sdk_cli.validators.core import prepare_transaction_data_for_stake - -TESTDATA_FOLDER = Path(__file__).parent.joinpath("testdata") - - -def test_prepare_transaction_data_for_stake(): - node_operator_address = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") - validators_file_path = TESTDATA_FOLDER / "validators.json" - data, gas_limit = prepare_transaction_data_for_stake( - node_operator_address, validators_file_path, node_operator_address - ) - - assert ( - data - == "stake@03@E7BEAA95B3877F47348DF4DD1CB578A4F7CABF7A20BFEEFE5CDD263878FF132B765E04FEF6F40C93512B666C47ED7719B8902F6C922C04247989B7137E837CC81A62E54712471C97A2DDAB75AA9C2F58F813ED4C0FA722BDE0AB718BFF382208@604882237A9845F508AD03877B5AAB90569683EEB51FAFCBBEB87440BA359992B3C0B837A8757C25BE18132549404F88@78689FD4B1E2E434D567FE01E61598A42717D83124308266BD09CCC15D2339DD318C019914B86AC29ADBAE5DD8A02D0307425E9BD85A296E94943708C72F8C670F0B7C50A890A5719088DBD9F1D062CAD9ACFFA06DF834106EEBE1A4257EF00D@EC54A009695AF56C3585EF623387B67B6DF1974B0B3C9138EB64BDE6EB33978AE9851112B20C99BF63588E8E949E4388@7188B234A8BF834F2E6258012AA09A2AB93178FFAB9C789480275F61FE02CD1B9A58DDC63B79A73ABEA9E2B7AC5CAC0B0D4324EFF50ACA2F0EC946B9AE6797511FA3CE461B57E77129CBA8AB3B51147695D4CE889CBE67905F6586B4E4F22491@C6C637DE17DB5F89A2FA1D1D935CB60C0E5E8958D3BFC47F903F774DD97398C8FE22093E113865EE98C3AFDD1DE62694@0139472EFF6886771A982F3083DA5D421F24C29181E63888228DC81CA60D69E1" - ) - assert gas_limit == 16464500 diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index 8af6bb14..fb5bf164 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -1,28 +1,19 @@ import json import logging import time -from pathlib import Path -from typing import Any, List, Optional, Protocol, TextIO, Union +from typing import Optional, Protocol, TextIO, Union from multiversx_sdk import ( - Account, Address, - LedgerAccount, - Token, - TokenComputer, TokenTransfer, Transaction, - TransactionComputer, TransactionOnNetwork, TransactionsFactoryConfig, TransferTransactionsFactory, ) -from multiversx_sdk_cli import config, errors +from multiversx_sdk_cli import errors from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController -from multiversx_sdk_cli.cli_password import load_guardian_password, load_password -from multiversx_sdk_cli.cosign_transaction import cosign_transaction -from multiversx_sdk_cli.errors import IncorrectWalletError, NoWalletProvided from multiversx_sdk_cli.interfaces import IAccount logger = logging.getLogger("transactions") @@ -99,165 +90,6 @@ def create_transaction( return transaction -def do_prepare_transaction(args: Any) -> Transaction: - account = load_sender_account_from_args(args) - - native_amount = int(args.value) - transfers = getattr(args, "token_transfers", []) - transfers = prepare_token_transfers(transfers) if transfers else None - - config = TransactionsFactoryConfig(args.chain) - factory = TransferTransactionsFactory(config) - receiver = Address.new_from_bech32(args.receiver) - - if native_amount or transfers: - tx = factory.create_transaction_for_transfer( - sender=account.address, - receiver=receiver, - native_amount=native_amount, - token_transfers=transfers, - data=str(args.data).encode(), - ) - else: - # this is for transactions with no token transfers(egld/esdt); useful for setting the data field - tx = Transaction( - sender=account.address, - receiver=receiver, - data=str(args.data).encode(), - gas_limit=int(args.gas_limit), - chain_id=args.chain, - ) - - tx.gas_limit = int(args.gas_limit) - tx.sender_username = getattr(args, "sender_username", None) or "" - tx.receiver_username = getattr(args, "receiver_username", None) or "" - tx.gas_price = int(args.gas_price) - tx.nonce = int(args.nonce) - tx.value = int(args.value) - tx.version = int(args.version) - tx.options = int(args.options) - - tx_computer = TransactionComputer() - if isinstance(account, LedgerAccount): - tx_computer.apply_options_for_hash_signing(tx) - - if args.guardian: - tx.guardian = Address.new_from_bech32(args.guardian) - - if args.relayer: - tx.relayer = Address.new_from_bech32(args.relayer) - - try: - relayer_account = load_relayer_account_from_args(args) - if relayer_account.address != tx.relayer: - raise IncorrectWalletError("") - - if isinstance(relayer_account, LedgerAccount): - tx_computer.apply_options_for_hash_signing(tx) - - tx.relayer_signature = relayer_account.sign_transaction(tx) - except NoWalletProvided: - logger.warning("Relayer wallet not provided. Transaction will not be signed by relayer.") - except IncorrectWalletError: - raise IncorrectWalletError("Relayer wallet does not match the relayer's address set in the transaction.") - - try: - guardian_account = get_guardian_account_from_args(args) - if isinstance(guardian_account, LedgerAccount): - tx_computer.apply_options_for_hash_signing(tx) - - except NoWalletProvided: - guardian_account = None - - tx.signature = account.sign_transaction(tx) - tx = sign_tx_by_guardian(args, tx, guardian_account) - - return tx - - -def load_sender_account_from_args(args: Any) -> IAccount: - hrp = config.get_address_hrp() - - if args.pem: - return Account.new_from_pem(file_path=Path(args.pem), index=args.pem_index, hrp=hrp) - elif args.keyfile: - password = load_password(args) - return Account.new_from_keystore( - file_path=Path(args.keyfile), - password=password, - address_index=args.address_index, - hrp=hrp, - ) - elif args.ledger: - return LedgerAccount(address_index=args.ledger_address_index) - else: - raise errors.NoWalletProvided() - - -def load_relayer_account_from_args(args: Any) -> IAccount: - hrp = config.get_address_hrp() - - if args.relayer_ledger: - return LedgerAccount(address_index=args.relayer_ledger_address_index) - if args.relayer_pem: - return Account.new_from_pem(file_path=Path(args.relayer_pem), index=args.relayer_pem_index, hrp=hrp) - elif args.relayer_keyfile: - password = load_password(args) - return Account.new_from_keystore( - file_path=Path(args.relayer_keyfile), - password=password, - address_index=args.relayer_address_index, - hrp=hrp, - ) - else: - raise errors.NoWalletProvided() - - -def prepare_token_transfers(transfers: List[Any]) -> List[TokenTransfer]: - token_computer = TokenComputer() - token_transfers: List[TokenTransfer] = [] - - for i in range(0, len(transfers) - 1, 2): - identifier = transfers[i] - amount = int(transfers[i + 1]) - nonce = token_computer.extract_nonce_from_extended_identifier(identifier) - - token = Token(identifier, nonce) - transfer = TokenTransfer(token, amount) - token_transfers.append(transfer) - - return token_transfers - - -def sign_tx_by_guardian(args: Any, tx: Transaction, guardian_account: Union[IAccount, None]) -> Transaction: - if guardian_account: - tx.guardian_signature = guardian_account.sign_transaction(tx) - elif args.guardian: - cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) - - return tx - - -# TODO: this is duplicated code; a proper refactoring will come later -def get_guardian_account_from_args(args: Any) -> IAccount: - hrp = config.get_address_hrp() - - if args.guardian_pem: - return Account.new_from_pem(file_path=Path(args.guardian_pem), index=args.guardian_pem_index, hrp=hrp) - elif args.guardian_keyfile: - password = load_guardian_password(args) - return Account.new_from_keystore( - file_path=Path(args.guardian_keyfile), - password=password, - address_index=args.guardian_address_index, - hrp=hrp, - ) - elif args.guardian_ledger: - return LedgerAccount(address_index=args.relayer_ledger_address_index) - else: - raise errors.NoWalletProvided() - - def send_and_wait_for_result(transaction: Transaction, proxy: INetworkProvider, timeout: int) -> TransactionOnNetwork: if not transaction.signature: raise errors.TransactionIsNotSigned() diff --git a/multiversx_sdk_cli/validators/__init__.py b/multiversx_sdk_cli/validators/__init__.py index 1c7ba1f4..e69de29b 100644 --- a/multiversx_sdk_cli/validators/__init__.py +++ b/multiversx_sdk_cli/validators/__init__.py @@ -1,31 +0,0 @@ -from multiversx_sdk_cli.validators.core import ( - prepare_args_for_change_reward_address, - prepare_args_for_claim, - prepare_args_for_clean_registered_data, - prepare_args_for_restake_unstaked_nodes, - prepare_args_for_stake, - prepare_args_for_unbond, - prepare_args_for_unbond_nodes, - prepare_args_for_unbond_tokens, - prepare_args_for_unjail, - prepare_args_for_unstake, - prepare_args_for_unstake_nodes, - prepare_args_for_unstake_tokens, -) -from multiversx_sdk_cli.validators.validators_file import ValidatorsFile - -__all__ = [ - "prepare_args_for_stake", - "prepare_args_for_unstake", - "prepare_args_for_unbond", - "prepare_args_for_unjail", - "prepare_args_for_change_reward_address", - "prepare_args_for_claim", - "prepare_args_for_unstake_nodes", - "prepare_args_for_unstake_tokens", - "prepare_args_for_unbond_nodes", - "prepare_args_for_unbond_tokens", - "prepare_args_for_clean_registered_data", - "prepare_args_for_restake_unstaked_nodes", - "ValidatorsFile", -] diff --git a/multiversx_sdk_cli/validators/core.py b/multiversx_sdk_cli/validators/core.py index de37b597..e7c6e194 100644 --- a/multiversx_sdk_cli/validators/core.py +++ b/multiversx_sdk_cli/validators/core.py @@ -1,13 +1,21 @@ import logging from pathlib import Path -from typing import Any, List, Tuple, Union - -from multiversx_sdk import Address, ValidatorPEM, ValidatorSigner - -from multiversx_sdk_cli import cli_shared, utils +from typing import Any, Optional, Union + +from multiversx_sdk import Address, Transaction +from multiversx_sdk.abi import ( + AddressValue, + BigUIntValue, + BytesValue, + Serializer, + U32Value, +) + +from multiversx_sdk_cli import utils from multiversx_sdk_cli.config import MetaChainSystemSCsCost, get_address_hrp from multiversx_sdk_cli.constants import GAS_PER_DATA_BYTE, MIN_GAS_LIMIT -from multiversx_sdk_cli.contracts import prepare_execute_transaction_data +from multiversx_sdk_cli.interfaces import IAccount +from multiversx_sdk_cli.transactions import TransactionsController from multiversx_sdk_cli.validators.validators_file import ValidatorsFile logger = logging.getLogger("validators") @@ -15,165 +23,605 @@ VALIDATORS_SMART_CONTRACT_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000001ffff" -def prepare_args_for_stake(args: Any): - if args.top_up: - prepare_args_for_top_up(args) - return - - node_operator = cli_shared.prepare_account(args) - - validators_file_path = Path(args.validators_file) - reward_address = Address.new_from_bech32(args.reward_address) if args.reward_address else None - - data, gas_limit = prepare_transaction_data_for_stake(node_operator.address, validators_file_path, reward_address) - args.data = data - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = gas_limit - - -def prepare_transaction_data_for_stake( - node_operator_address: Address, - validators_file_path: Path, - reward_address: Union[Address, None], -) -> Tuple[str, int]: - validators_file = ValidatorsFile(validators_file_path) - num_of_nodes = validators_file.get_num_of_nodes() - validators_list = validators_file.get_validators_list() - - call_arguments: List[Any] = [] - call_arguments.append(num_of_nodes) - - for validator in validators_list: - # Get path of "pemFile", make it absolute - validator_pem = Path(validator.get("pemFile")).expanduser() - validator_pem = validator_pem if validator_pem.is_absolute() else validators_file_path.parent / validator_pem - - pem_file = ValidatorPEM.from_file(validator_pem) - - validator_signer = ValidatorSigner(pem_file.secret_key) - signed_message = validator_signer.sign(node_operator_address.pubkey).hex() - - call_arguments.append(f"0x{pem_file.secret_key.generate_public_key().hex()}") - call_arguments.append(f"0x{signed_message}") - - if reward_address: - call_arguments.append(f"0x{reward_address.to_hex()}") - - data = prepare_execute_transaction_data("stake", call_arguments) - gas_limit = estimate_system_sc_call(str(data), MetaChainSystemSCsCost.STAKE, num_of_nodes) - - return str(data), gas_limit - - -def prepare_args_for_top_up(args: Any): - args.data = "stake" - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.STAKE, 1) - - -def prepare_args_for_unstake(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = "unStake" + parsed_keys - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNSTAKE, num_keys) - - -def prepare_args_for_unbond(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = "unBond" + parsed_keys - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNBOND, num_keys) - - -def prepare_args_for_unjail(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = "unJail" + parsed_keys - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNJAIL, num_keys) - - -def prepare_args_for_change_reward_address(args: Any): - reward_address = Address.new_from_bech32(args.reward_address) - args.data = "changeRewardAddress@" + reward_address.hex() - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.CHANGE_REWARD_ADDRESS) - - -def prepare_args_for_claim(args: Any): - args.data = "claim" - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.CLAIM) - - -def prepare_args_for_unstake_nodes(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = "unStakeNodes" + parsed_keys - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNSTAKE, num_keys) - - -def prepare_args_for_unstake_tokens(args: Any): - args.data = "unStakeTokens" - args.data += "@" + utils.str_int_to_hex_str(str(args.unstake_value)) - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNSTAKE_TOKENS) - - -def prepare_args_for_unbond_nodes(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = "unBondNodes" + parsed_keys - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNBOND, num_keys) - - -def prepare_args_for_unbond_tokens(args: Any): - args.data = "unBondTokens" - args.data += "@" + utils.str_int_to_hex_str(str(args.unbond_value)) - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.UNBOND_TOKENS) - - -def prepare_args_for_clean_registered_data(args: Any): - args.data = "cleanRegisteredData" - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.STAKE) - - -def prepare_args_for_restake_unstaked_nodes(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.nodes_public_keys) - args.data = "reStakeUnStakedNodes" + parsed_keys - - args.receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()).to_bech32() - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.STAKE, num_keys) - - -def estimate_system_sc_call(transaction_data: str, base_cost: int, factor: int = 1): - num_bytes = len(transaction_data) - gas_limit = MIN_GAS_LIMIT + num_bytes * GAS_PER_DATA_BYTE - gas_limit += factor * base_cost - return gas_limit +class ValidatorsController: + def __init__(self, chain_id: str) -> None: + self.transactions_controller = TransactionsController(chain_id) + self.serializer = Serializer() + + def create_transaction_for_staking( + self, + sender: IAccount, + validators_file: Path, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + rewards_address: Optional[Address] = None, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + validators = ValidatorsFile(validators_file) + data = self.prepare_transaction_data_for_stake( + node_operator=sender.address, + validators_file=validators, + rewards_address=rewards_address, + ) + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.STAKE, validators.get_num_of_nodes()) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def prepare_transaction_data_for_stake( + self, + node_operator: Address, + validators_file: ValidatorsFile, + rewards_address: Union[Address, None], + ) -> str: + num_of_nodes = validators_file.get_num_of_nodes() + + call_arguments: list[Any] = [] + call_arguments.append(U32Value(num_of_nodes)) + + validator_signers = validators_file.load_signers() + + for validator in validator_signers: + signed_message = validator.sign(node_operator.get_public_key()) + + call_arguments.append(BytesValue(validator.secret_key.generate_public_key().buffer)) + call_arguments.append(BytesValue(signed_message)) + + if rewards_address: + call_arguments.append(AddressValue.new_from_address(rewards_address)) + + data = "stake@" + self.serializer.serialize(call_arguments) + return data + + def create_transaction_for_topping_up( + self, + sender: IAccount, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + data = "stake" + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.STAKE, 1) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_unstaking( + self, + sender: IAccount, + keys: str, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + parsed_keys, num_keys = utils.parse_keys(keys) + data = f"unStake{parsed_keys}" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.UNSTAKE, num_keys) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_unjailing( + self, + sender: IAccount, + keys: str, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + parsed_keys, num_keys = utils.parse_keys(keys) + data = f"unJail{parsed_keys}" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.UNJAIL, num_keys) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_unbonding( + self, + sender: IAccount, + keys: str, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + parsed_keys, num_keys = utils.parse_keys(keys) + data = f"unBond{parsed_keys}" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.UNBOND, num_keys) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_changing_rewards_address( + self, + sender: IAccount, + rewards_address: Address, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + data = f"changeRewardAddress@{rewards_address.to_hex()}" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.CHANGE_REWARD_ADDRESS) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_claiming( + self, + sender: IAccount, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + data = "claim" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.CLAIM) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_unstaking_nodes( + self, + sender: IAccount, + keys: str, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + parsed_keys, num_keys = utils.parse_keys(keys) + data = f"unStakeNodes{parsed_keys}" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.UNSTAKE, num_keys) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_unstaking_tokens( + self, + sender: IAccount, + value: int, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + data = f"unStakeTokens@{self.serializer.serialize([BigUIntValue(value)])}" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.UNSTAKE_TOKENS) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_unbonding_nodes( + self, + sender: IAccount, + keys: str, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + parsed_keys, num_keys = utils.parse_keys(keys) + data = f"unBondNodes{parsed_keys}" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.UNBOND, num_keys) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_unbonding_tokens( + self, + sender: IAccount, + value: int, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + data = f"unBondTokens@{self.serializer.serialize([BigUIntValue(value)])}" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.UNBOND_TOKENS) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_cleaning_registered_data( + self, + sender: IAccount, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + data = "cleanRegisteredData" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.CLEAN_REGISTERED_DATA) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def create_transaction_for_restaking_unstaked_nodes( + self, + sender: IAccount, + keys: str, + native_amount: int, + estimate_gas: bool, + gas_limit: int, + gas_price: int, + nonce: int, + version: int, + options: int, + guardian_account: Optional[IAccount] = None, + guardian_address: Optional[Address] = None, + relayer_account: Optional[IAccount] = None, + relayer_address: Optional[Address] = None, + guardian_service_url: str = "", + guardian_2fa_code: str = "", + ) -> Transaction: + parsed_keys, num_keys = utils.parse_keys(keys) + data = f"reStakeUnStakedNodes{parsed_keys}" + + if estimate_gas: + gas_limit = self.estimate_system_sc_call(data, MetaChainSystemSCsCost.RESTAKE_UNSTAKED_NODES, num_keys) + + receiver = Address.new_from_hex(VALIDATORS_SMART_CONTRACT_ADDRESS_HEX, get_address_hrp()) + + return self.transactions_controller.create_transaction( + sender=sender, + receiver=receiver, + native_amount=native_amount, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + version=version, + options=options, + data=data, + guardian_account=guardian_account, + guardian_address=guardian_address, + relayer_account=relayer_account, + relayer_address=relayer_address, + guardian_service_url=guardian_service_url, + guardian_2fa_code=guardian_2fa_code, + ) + + def estimate_system_sc_call(self, transaction_data: str, base_cost: int, factor: int = 1): + num_bytes = len(transaction_data) + gas_limit = MIN_GAS_LIMIT + num_bytes * GAS_PER_DATA_BYTE + gas_limit += factor * base_cost + return gas_limit diff --git a/multiversx_sdk_cli/validators/validators_file.py b/multiversx_sdk_cli/validators/validators_file.py index 2543180a..c0060f44 100644 --- a/multiversx_sdk_cli/validators/validators_file.py +++ b/multiversx_sdk_cli/validators/validators_file.py @@ -1,6 +1,5 @@ import json from pathlib import Path -from typing import Dict, List from multiversx_sdk import ValidatorPEM, ValidatorPublicKey, ValidatorSigner @@ -19,8 +18,8 @@ def get_num_of_nodes(self) -> int: def get_validators_list(self): return self._validators_data.get("validators", []) - def load_signers(self) -> List[ValidatorSigner]: - signers: List[ValidatorSigner] = [] + def load_signers(self) -> list[ValidatorSigner]: + signers: list[ValidatorSigner] = [] for validator in self.get_validators_list(): pem_file = self._load_validator_pem(validator) validator_signer = ValidatorSigner(pem_file.secret_key) @@ -28,8 +27,8 @@ def load_signers(self) -> List[ValidatorSigner]: return signers - def load_public_keys(self) -> List[ValidatorPublicKey]: - public_keys: List[ValidatorPublicKey] = [] + def load_public_keys(self) -> list[ValidatorPublicKey]: + public_keys: list[ValidatorPublicKey] = [] for validator in self.get_validators_list(): pem_file = self._load_validator_pem(validator) @@ -37,7 +36,7 @@ def load_public_keys(self) -> List[ValidatorPublicKey]: return public_keys - def _load_validator_pem(self, validator: Dict[str, str]) -> ValidatorPEM: + def _load_validator_pem(self, validator: dict[str, str]) -> ValidatorPEM: # Get path of "pemFile", make it absolute validator_pem = Path(validator.get("pemFile", "")).expanduser() validator_pem = (