diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index f6a67cde..94f017c7 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -228,7 +228,7 @@ def _add_contract_arg(sub: Any): def _add_contract_abi_arg(sub: Any): - sub.add_argument("--abi", help="the ABI of the Smart Contract") + sub.add_argument("--abi", type=str, help="the ABI of the Smart Contract") def _add_function_arg(sub: Any): @@ -239,7 +239,7 @@ def _add_arguments_arg(sub: Any): sub.add_argument("--arguments", nargs='+', help="arguments for the contract transaction, as [number, bech32-address, ascii string, " "boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 0xabba str:TOK-a1c2ef true erd1[..]") - sub.add_argument("--arguments-file", help="a json file containing the arguments. ONLY if abi file is provided. " + sub.add_argument("--arguments-file", type=str, help="a json file containing the arguments. ONLY if abi file is provided. " "E.g. { 'to': 'erd1...', 'amount': 10000000000 }") @@ -372,25 +372,24 @@ def call(args: Any): cli_shared.prepare_nonce_in_args(args) sender = cli_shared.prepare_account(args) - abi = Abi.load(Path(args.abi)) if args.abi else None config = TransactionsFactoryConfig(args.chain) + abi = Abi.load(Path(args.abi)) if args.abi else None contract = SmartContract(config, abi) - contract_address = Address.new_from_bech32(args.contract) - - json_args = json.loads(Path(args.arguments_json).expanduser().read_text()) if args.arguments_json else None + json_args = json.loads(Path(args.arguments_file).expanduser().read_text()) if args.arguments_file else None if json_args and args.arguments: raise Exception("Both '--arguments' and '--arguments-json' provided.") - # check what kind of args were provided and pass them further. + arguments = json_args or args.arguments + contract_address = Address.new_from_bech32(args.contract) tx = contract.prepare_execute_transaction( caller=sender, contract=contract_address, function=args.function, - arguments=args.arguments, + arguments=arguments, gas_limit=int(args.gas_limit), value=int(args.value), transfers=args.token_transfers, diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 6bf3b7a0..754130fd 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -1,7 +1,7 @@ import base64 import logging from pathlib import Path -from typing import Any, List, Optional, Protocol, Sequence, Union +from typing import Any, Dict, List, Optional, Protocol, Sequence, Union, cast from multiversx_sdk import (Address, SmartContractTransactionsFactory, Token, TokenComputer, TokenTransfer, Transaction, @@ -75,6 +75,7 @@ class IConfig(Protocol): class SmartContract: def __init__(self, config: IConfig, abi: Optional[Abi] = None): + self._abi = abi self._factory = SmartContractTransactionsFactory(config, abi) def prepare_deploy_transaction(self, @@ -91,7 +92,7 @@ def prepare_deploy_transaction(self, version: int, options: int, guardian: str) -> Transaction: - args = prepare_args_for_factory(arguments) if arguments else [] + args = self.prepare_args_for_factory("constructor", arguments) if arguments else [] tx = self._factory.create_transaction_for_deploy( sender=owner.address, @@ -116,7 +117,7 @@ def prepare_execute_transaction(self, caller: Account, contract: Address, function: str, - arguments: Union[List[str], None], + arguments: Any, gas_limit: int, value: int, transfers: Union[List[str], None], @@ -125,7 +126,7 @@ def prepare_execute_transaction(self, options: int, guardian: str) -> Transaction: token_transfers = self._prepare_token_transfers(transfers) if transfers else [] - args = prepare_args_for_factory(arguments) if arguments else [] + args = self.prepare_args_for_factory(function, arguments) if arguments else [] tx = self._factory.create_transaction_for_execute( sender=caller.address, @@ -159,7 +160,7 @@ def prepare_upgrade_transaction(self, version: int, options: int, guardian: str) -> Transaction: - args = prepare_args_for_factory(arguments) if arguments else [] + args = self.prepare_args_for_factory("upgrade_constructor", arguments) if arguments else [] tx = self._factory.create_transaction_for_upgrade( sender=owner.address, @@ -196,6 +197,46 @@ def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: return token_transfers + def prepare_args_for_factory(self, endpoint: str, arguments: Any) -> List[Any]: + args: List[Any] = [] + + if isinstance(arguments, dict): + return self._prepare_args_using_abi(endpoint, arguments) + + if isinstance(arguments, list): + for arg in arguments: # type: ignore + arg = cast(str, arg) + if arg.startswith(HEX_PREFIX): + args.append(hex_to_bytes(arg)) + elif arg.isnumeric(): + args.append(int(arg)) + elif arg.startswith(DEFAULT_HRP): + args.append(Address.new_from_bech32(arg)) + elif arg.lower() == FALSE_STR_LOWER: + args.append(False) + elif arg.lower() == TRUE_STR_LOWER: + args.append(True) + elif arg.startswith(STR_PREFIX): + args.append(arg[len(STR_PREFIX):]) + else: + raise errors.BadUserInput(f"Unknown argument type for argument: `{arg}`. Use `mxpy contract --help` to check all supported arguments") + + return args + + def _prepare_args_using_abi(self, function: str, arguments: Dict[str, Any]): + if not self._abi: + raise Exception("Abi file not provided") + + endpoint_definition = [endpoint for endpoint in self._abi.definition.endpoints if endpoint.name == function] + if not endpoint_definition: + raise Exception(f"Endpoint [{function}] not found") + elif len(endpoint_definition) > 1: + raise Exception(f"More than one endpoint with name [{function}] found.") + else: + endpoint_definition = endpoint_definition[0] + + input_parameters_names = [input.name for input in endpoint_definition.inputs] + def query_contract( contract_address: IAddress, @@ -265,28 +306,6 @@ def prepare_execute_transaction_data(function: str, arguments: List[Any]) -> Tra return TransactionPayload.from_str(tx_data) -def prepare_args_for_factory(arguments: List[str]) -> List[Any]: - args: List[Any] = [] - - for arg in arguments: - if arg.startswith(HEX_PREFIX): - args.append(hex_to_bytes(arg)) - elif arg.isnumeric(): - args.append(int(arg)) - elif arg.startswith(DEFAULT_HRP): - args.append(Address.new_from_bech32(arg)) - elif arg.lower() == FALSE_STR_LOWER: - args.append(False) - elif arg.lower() == TRUE_STR_LOWER: - args.append(True) - elif arg.startswith(STR_PREFIX): - args.append(arg[len(STR_PREFIX):]) - else: - raise errors.BadUserInput(f"Unknown argument type for argument: `{arg}`. Use `mxpy contract --help` to check all supported arguments") - - return args - - def hex_to_bytes(arg: str): argument = arg[len(HEX_PREFIX):] argument = argument.upper() diff --git a/multiversx_sdk_cli/tests/test_contracts.py b/multiversx_sdk_cli/tests/test_contracts.py index de2a507b..d44bb3f9 100644 --- a/multiversx_sdk_cli/tests/test_contracts.py +++ b/multiversx_sdk_cli/tests/test_contracts.py @@ -3,14 +3,14 @@ import pytest from Cryptodome.Hash import keccak -from multiversx_sdk import Address +from multiversx_sdk import Address, TransactionsFactoryConfig from multiversx_sdk_cli import errors from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.contract_verification import _create_request_signature -from multiversx_sdk_cli.contracts import (_interpret_as_number_if_safely, - _prepare_argument, - prepare_args_for_factory) +from multiversx_sdk_cli.contracts import (SmartContract, + _interpret_as_number_if_safely, + _prepare_argument) logging.basicConfig(level=logging.INFO) @@ -70,13 +70,14 @@ def test_interpret_as_number_if_safely(): def test_prepare_args_for_factories(): + sc = SmartContract(TransactionsFactoryConfig("mock")) args = [ "0x5", "123", "false", "true", "str:test-string", "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" ] - arguments = prepare_args_for_factory(args) + arguments = sc.prepare_args_for_factory(args) assert arguments[0] == b"\x05" assert arguments[1] == 123 assert arguments[2] == False