diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index cb8ea41e..0ca7149e 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -13,7 +13,7 @@ from multiversx_sdk_cli.constants import NUMBER_OF_SHARDS from multiversx_sdk_cli.contract_verification import \ trigger_contract_verification -from multiversx_sdk_cli.contracts import SmartContract, query_contract +from multiversx_sdk_cli.contracts import SmartContract from multiversx_sdk_cli.cosign_transaction import cosign_transaction from multiversx_sdk_cli.dependency_checker import check_if_rust_is_installed from multiversx_sdk_cli.docker import is_docker_installed, run_docker @@ -132,6 +132,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: sub = cli_shared.add_command_subparser(subparsers, "contract", "query", "Query a Smart Contract (call a pure function)") _add_contract_arg(sub) + _add_contract_abi_arg(sub) cli_shared.add_proxy_arg(sub) _add_function_arg(sub) _add_arguments_arg(sub) @@ -443,17 +444,28 @@ def upgrade(args: Any): def query(args: Any): logger.debug("query") - # workaround so we can use the function bellow + # workaround so we can use the function below to set chainID args.chain = "" cli_shared.prepare_chain_id_in_args(args) + config = TransactionsFactoryConfig(args.chain) + abi = Abi.load(Path(args.abi)) if args.abi else None + contract = SmartContract(config, abi) + + arguments, should_prepare_args = _get_contract_arguments(args) contract_address = Address.new_from_bech32(args.contract) proxy = ProxyNetworkProvider(args.proxy) function = args.function - arguments: List[Any] = args.arguments or [] - result = query_contract(contract_address, proxy, function, arguments) + result = contract.query_contract( + contract_address=contract_address, + proxy=proxy, + function=function, + arguments=arguments, + should_prepare_args=should_prepare_args + ) + utils.dump_out_json(result) diff --git a/multiversx_sdk_cli/cli_dns.py b/multiversx_sdk_cli/cli_dns.py index a7c8dcd6..1e87358e 100644 --- a/multiversx_sdk_cli/cli_dns.py +++ b/multiversx_sdk_cli/cli_dns.py @@ -9,6 +9,7 @@ dns_address_for_name, name_hash, register, registration_cost, resolve, validate_name, version) +from multiversx_sdk_cli.errors import ArgumentsNotProvidedError def setup_parser(args: List[str], subparsers: Any) -> Any: @@ -32,7 +33,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: sub = cli_shared.add_command_subparser(subparsers, "dns", "validate-name", "Asks one of the DNS contracts to validate a name. Can be useful before registering it.") _add_name_arg(sub) - sub.add_argument("--shard-id", default=0, help="shard id of the contract to call (default: %(default)s)") + sub.add_argument("--shard-id", type=int, default=0, help="shard id of the contract to call (default: %(default)s)") cli_shared.add_proxy_arg(sub) sub.set_defaults(func=dns_validate_name) @@ -41,12 +42,12 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: sub.set_defaults(func=get_name_hash) sub = cli_shared.add_command_subparser(subparsers, "dns", "registration-cost", "Gets the registration cost from a DNS smart contract, by default the one with shard id 0.") - sub.add_argument("--shard-id", default=0, help="shard id of the contract to call (default: %(default)s)") + sub.add_argument("--shard-id", type=int, default=0, help="shard id of the contract to call (default: %(default)s)") cli_shared.add_proxy_arg(sub) sub.set_defaults(func=get_registration_cost) sub = cli_shared.add_command_subparser(subparsers, "dns", "version", "Asks the contract for its version") - sub.add_argument("--shard-id", default=0, help="shard id of the contract to call (default: %(default)s)") + sub.add_argument("--shard-id", type=int, default=0, help="shard id of the contract to call (default: %(default)s)") sub.add_argument("--all", action="store_true", default=False, help="prints a list of all DNS contracts and their current versions (default: %(default)s)") cli_shared.add_proxy_arg(sub) sub.set_defaults(func=get_version) @@ -70,13 +71,21 @@ def _add_name_arg(sub: Any): sub.add_argument("name", help="the name for which to check") +def _ensure_proxy_is_provided(args: Any): + if not args.proxy: + raise ArgumentsNotProvidedError("'--proxy' argument not provided") + + def dns_resolve(args: Any): + _ensure_proxy_is_provided(args) + addr = resolve(args.name, ProxyNetworkProvider(args.proxy)) if addr.to_hex() != Address.new_from_bech32(ADDRESS_ZERO_BECH32).to_hex(): print(addr.to_bech32()) def dns_validate_name(args: Any): + _ensure_proxy_is_provided(args) validate_name(args.name, args.shard_id, ProxyNetworkProvider(args.proxy)) @@ -97,10 +106,13 @@ def get_dns_address_for_name_hex(args: Any): def get_registration_cost(args: Any): + _ensure_proxy_is_provided(args) print(registration_cost(args.shard_id, ProxyNetworkProvider(args.proxy))) def get_version(args: Any): + _ensure_proxy_is_provided(args) + proxy = ProxyNetworkProvider(args.proxy) if args.all: t = PrettyTable(['Shard ID', 'Contract address (bech32)', 'Contract address (hex)', 'Version']) diff --git a/multiversx_sdk_cli/cli_wallet.py b/multiversx_sdk_cli/cli_wallet.py index f9433fd3..eeae9ebb 100644 --- a/multiversx_sdk_cli/cli_wallet.py +++ b/multiversx_sdk_cli/cli_wallet.py @@ -116,21 +116,7 @@ def wallet_new(args: Any): shard = args.shard if shard is not None: - if shard not in CURRENT_SHARDS: - raise BadUserInput(f"Wrong shard provided. Choose between {CURRENT_SHARDS}") - - is_wallet_generated = False - for _ in range(MAX_ITERATIONS_FOR_GENERATING_WALLET): - mnemonic = Mnemonic.generate() - pubkey = mnemonic.derive_key().generate_public_key() - generated_address_shard = get_shard_of_pubkey(pubkey.buffer, NUMBER_OF_SHARDS) - - if shard == generated_address_shard: - is_wallet_generated = True - break - - if not is_wallet_generated: - raise WalletGenerationError(f"Couldn't generate wallet in shard {shard}") + mnemonic = _generate_mnemonic_with_shard_constraint(shard) else: mnemonic = Mnemonic.generate() @@ -169,6 +155,22 @@ def wallet_new(args: Any): logger.info(f"Wallet ({format}) saved: {outfile}") +def _generate_mnemonic_with_shard_constraint(shard: int) -> Mnemonic: + + if shard not in CURRENT_SHARDS: + raise BadUserInput(f"Wrong shard provided. Choose between {CURRENT_SHARDS}") + + for _ in range(MAX_ITERATIONS_FOR_GENERATING_WALLET): + mnemonic = Mnemonic.generate() + pubkey = mnemonic.derive_key().generate_public_key() + generated_address_shard = get_shard_of_pubkey(pubkey.buffer, NUMBER_OF_SHARDS) + + if shard == generated_address_shard: + return mnemonic + + raise WalletGenerationError(f"Couldn't generate wallet in shard {shard}") + + def convert_wallet(args: Any): infile = Path(args.infile).expanduser().resolve() if args.infile else None outfile = Path(args.outfile).expanduser().resolve() if args.outfile else None diff --git a/multiversx_sdk_cli/contract_verification.py b/multiversx_sdk_cli/contract_verification.py index 01e833bb..cbad2d3d 100644 --- a/multiversx_sdk_cli/contract_verification.py +++ b/multiversx_sdk_cli/contract_verification.py @@ -112,7 +112,7 @@ def _create_request_signature(account: Account, contract_address: Address, reque def query_status_with_task_id(url: str, task_id: str, interval: int = 10): - logger.info(f"Please wait while we verify your contract. This may take a while.") + logger.info("Please wait while we verify your contract. This may take a while.") old_status = "" while True: @@ -120,7 +120,7 @@ def query_status_with_task_id(url: str, task_id: str, interval: int = 10): status = response.get("status", "") if status == "finished": - logger.info(f"Verification finished!") + logger.info("Verification finished!") dump_out_json(response) break elif status != old_status: diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 5ebd8fe2..98885dc4 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -1,19 +1,18 @@ -import base64 import logging from pathlib import Path -from typing import Any, List, Optional, Protocol, Sequence, Union +from typing import Any, List, Optional, Protocol, Union -from multiversx_sdk import (Address, SmartContractTransactionsFactory, Token, +from multiversx_sdk import (Address, QueryRunnerAdapter, + SmartContractQueriesController, + SmartContractTransactionsFactory, Token, TokenComputer, TokenTransfer, Transaction, TransactionPayload) from multiversx_sdk.abi import Abi -from multiversx_sdk.network_providers.interface import IContractQuery from multiversx_sdk_cli import errors from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.constants import DEFAULT_HRP from multiversx_sdk_cli.interfaces import IAddress -from multiversx_sdk_cli.utils import Object logger = logging.getLogger("contracts") @@ -28,41 +27,14 @@ def query_contract(self, query: Any) -> 'IContractQueryResponse': ... -class QueryResult(Object): - def __init__(self, as_base64: str, as_hex: str, as_number: Optional[int]): - self.base64 = as_base64 - self.hex = as_hex - self.number = as_number - - -class ContractQuery(IContractQuery): - def __init__(self, address: IAddress, function: str, value: int, arguments: List[bytes], caller: Optional[IAddress] = None): - self.contract = address - self.function = function - self.caller = caller - self.value = value - self.encoded_arguments = [item.hex() for item in arguments] - - def get_contract(self) -> IAddress: - return self.contract - - def get_function(self) -> str: - return self.function - - def get_encoded_arguments(self) -> Sequence[str]: - return self.encoded_arguments - - def get_caller(self) -> Optional[IAddress]: - return self.caller - - def get_value(self) -> int: - return self.value - - class IContractQueryResponse(Protocol): return_data: List[str] return_code: str return_message: str + gas_used: int + + def get_return_data_parts(self) -> List[bytes]: + ... class IConfig(Protocol): @@ -95,7 +67,7 @@ def prepare_deploy_transaction(self, guardian: str) -> Transaction: args = arguments if arguments else [] if should_prepare_args: - args = self.prepare_args_for_factory(args) + args = self._prepare_args_for_factory(args) tx = self._factory.create_transaction_for_deploy( sender=owner.address, @@ -133,7 +105,7 @@ def prepare_execute_transaction(self, args = arguments if arguments else [] if should_prepare_args: - args = self.prepare_args_for_factory(args) + args = self._prepare_args_for_factory(args) tx = self._factory.create_transaction_for_execute( sender=caller.address, @@ -170,7 +142,7 @@ def prepare_upgrade_transaction(self, guardian: str) -> Transaction: args = arguments if arguments else [] if should_prepare_args: - args = self.prepare_args_for_factory(args) + args = self._prepare_args_for_factory(args) tx = self._factory.create_transaction_for_upgrade( sender=owner.address, @@ -192,6 +164,30 @@ def prepare_upgrade_transaction(self, return tx + def query_contract(self, + contract_address: IAddress, + proxy: INetworkProvider, + function: str, + arguments: Optional[List[Any]], + should_prepare_args: bool) -> List[Any]: + args = arguments if arguments else [] + if should_prepare_args: + args = self._prepare_args_for_factory(args) + + query_runner = QueryRunnerAdapter(proxy) + sc_query_controller = SmartContractQueriesController(query_runner, self._abi) + + try: + response = sc_query_controller.query( + contract=contract_address.to_bech32(), + function=function, + arguments=args + ) + except Exception as e: + raise errors.QueryContractError("Couldn't query contract: ", e) + + return response + def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: token_computer = TokenComputer() token_transfers: List[TokenTransfer] = [] @@ -207,12 +203,12 @@ def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: return token_transfers - def prepare_args_for_factory(self, arguments: List[str]) -> List[Any]: + def _prepare_args_for_factory(self, arguments: List[str]) -> List[Any]: args: List[Any] = [] for arg in arguments: if arg.startswith(HEX_PREFIX): - args.append(hex_to_bytes(arg)) + args.append(self._hex_to_bytes(arg)) elif arg.isnumeric(): args.append(int(arg)) elif arg.startswith(DEFAULT_HRP): @@ -228,64 +224,11 @@ def prepare_args_for_factory(self, arguments: List[str]) -> List[Any]: return args - -def query_contract( - contract_address: IAddress, - proxy: INetworkProvider, - function: str, - arguments: List[Any], - value: int = 0, - caller: Optional[IAddress] = None -) -> List[Any]: - response_data = query_detailed(contract_address, proxy, function, arguments, value, caller) - return_data = response_data.return_data - return [_interpret_return_data(data) for data in return_data] - - -def query_detailed(contract_address: IAddress, proxy: INetworkProvider, function: str, arguments: List[Any], - value: int = 0, caller: Optional[IAddress] = None) -> Any: - arguments = arguments or [] - # Temporary workaround, until we use sdk-core's serializer. - arguments_hex = [_prepare_argument(arg) for arg in arguments] - prepared_arguments_bytes = [bytes.fromhex(arg) for arg in arguments_hex] - - query = ContractQuery(contract_address, function, value, prepared_arguments_bytes, caller) - - response = proxy.query_contract(query) - # Temporary workaround, until we add "isSuccess" on the response class. - if response.return_code != "ok": - raise RuntimeError(f"Query failed: {response.return_message}") - return response - - -def _interpret_return_data(data: str) -> Any: - if not data: - return data - - try: - as_bytes = base64.b64decode(data) - as_hex = as_bytes.hex() - as_number = _interpret_as_number_if_safely(as_hex) - - result = QueryResult(data, as_hex, as_number) - return result - except Exception: - logger.warn(f"Cannot interpret return data: {data}") - return None - - -def _interpret_as_number_if_safely(as_hex: str) -> Optional[int]: - """ - Makes sure the string can be safely converted to an int (and then back to a string). - - See: - - https://stackoverflow.com/questions/73693104/valueerror-exceeds-the-limit-4300-for-integer-string-conversion - - https://github.com/python/cpython/issues/95778 - """ - try: - return int(str(int(as_hex or "0", 16))) - except Exception: - return None + def _hex_to_bytes(self, arg: str): + argument = arg[len(HEX_PREFIX):] + argument = argument.upper() + argument = ensure_even_length(argument) + return bytes.fromhex(argument) def prepare_execute_transaction_data(function: str, arguments: List[Any]) -> TransactionPayload: @@ -297,14 +240,7 @@ def prepare_execute_transaction_data(function: str, arguments: List[Any]) -> Tra return TransactionPayload.from_str(tx_data) -def hex_to_bytes(arg: str): - argument = arg[len(HEX_PREFIX):] - argument = argument.upper() - argument = ensure_even_length(argument) - return bytes.fromhex(argument) - - -# only used for contract queries and stake operations +# only used for stake operations def _prepare_argument(argument: Any): as_str = str(argument) as_hex = _to_hex(as_str) diff --git a/multiversx_sdk_cli/dns.py b/multiversx_sdk_cli/dns.py index 26fce0b9..114b1a11 100644 --- a/multiversx_sdk_cli/dns.py +++ b/multiversx_sdk_cli/dns.py @@ -1,12 +1,13 @@ from typing import Any, List, Protocol from Cryptodome.Hash import keccak -from multiversx_sdk import Address, AddressComputer +from multiversx_sdk import Address, AddressComputer, TransactionsFactoryConfig +from multiversx_sdk.network_providers.network_config import NetworkConfig from multiversx_sdk_cli import cli_shared, utils from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.constants import ADDRESS_ZERO_BECH32, DEFAULT_HRP -from multiversx_sdk_cli.contracts import query_contract +from multiversx_sdk_cli.contracts import SmartContract from multiversx_sdk_cli.transactions import (compute_relayed_v1_data, do_prepare_transaction) @@ -19,22 +20,48 @@ class INetworkProvider(Protocol): def query_contract(self, query: Any) -> Any: ... + def get_network_config(self) -> NetworkConfig: + ... + def resolve(name: str, proxy: INetworkProvider) -> Address: name_arg = "0x{}".format(str.encode(name).hex()) dns_address = dns_address_for_name(name) - result = query_contract(dns_address, proxy, "resolve", [name_arg]) - if len(result) == 0: + response = _query_contract( + contract_address=dns_address, + proxy=proxy, + function="resolve", + args=[name_arg] + ) + + if len(response) == 0: return Address.from_bech32(ADDRESS_ZERO_BECH32) - return Address.from_hex(result[0].hex, DEFAULT_HRP) + + result = response[0].get("returnDataParts")[0] + return Address.from_hex(result, DEFAULT_HRP) def validate_name(name: str, shard_id: int, proxy: INetworkProvider): name_arg = "0x{}".format(str.encode(name).hex()) dns_address = compute_dns_address_for_shard_id(shard_id) - query_contract(dns_address, proxy, "validateName", [name_arg]) + response = _query_contract( + contract_address=dns_address, + proxy=proxy, + function="validateName", + args=[name_arg] + ) + + response = response[0] + + return_code = response["returnCode"] + if return_code == "ok": + print(f"name [{name}] is valid") + else: + print(f"name [{name}] is invalid") + + print(response) def register(args: Any): @@ -69,17 +96,35 @@ def name_hash(name: str) -> bytes: def registration_cost(shard_id: int, proxy: INetworkProvider) -> int: dns_address = compute_dns_address_for_shard_id(shard_id) - result = query_contract(dns_address, proxy, "getRegistrationCost", []) - if len(result[0]) == 0: + + response = _query_contract( + contract_address=dns_address, + proxy=proxy, + function="getRegistrationCost", + args=[] + ) + + response = response[0] + + data = response["returnDataParts"][0] + if not data: return 0 else: - return int("0x{}".format(result[0])) + return int("0x{}".format(data)) def version(shard_id: int, proxy: INetworkProvider) -> str: dns_address = compute_dns_address_for_shard_id(shard_id) - result = query_contract(dns_address, proxy, "version", []) - return bytearray.fromhex(result[0].hex).decode() + + response = _query_contract( + contract_address=dns_address, + proxy=proxy, + function="version", + args=[] + ) + + response = response[0] + return bytearray.fromhex(response["returnDataParts"][0]).decode() def dns_address_for_name(name: str) -> Address: @@ -102,3 +147,17 @@ 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()) + + +def _query_contract(contract_address: Address, proxy: INetworkProvider, function: str, args: List[Any]) -> List[Any]: + chain_id = proxy.get_network_config().chain_id + config = TransactionsFactoryConfig(chain_id) + contract = SmartContract(config) + + return contract.query_contract( + contract_address=contract_address, + proxy=proxy, + function=function, + arguments=args, + should_prepare_args=False + ) diff --git a/multiversx_sdk_cli/errors.py b/multiversx_sdk_cli/errors.py index 8e4cf247..19dae17f 100644 --- a/multiversx_sdk_cli/errors.py +++ b/multiversx_sdk_cli/errors.py @@ -1,5 +1,3 @@ - -from pathlib import Path from typing import Any, List, Tuple, Union @@ -22,16 +20,6 @@ class ProgrammingError(KnownError): pass -class TemplateMissingError(KnownError): - def __init__(self, template: str): - super().__init__(f"Template missing: {template}") - - -class BadTemplateError(KnownError): - def __init__(self, directory: Path): - super().__init__(f"Bad template: {directory}") - - class DownloadError(KnownError): pass @@ -75,7 +63,7 @@ def __init__(self, directory: str): class BadFile(KnownError): - def __init__(self, filename: str, inner=None): + def __init__(self, filename: str, inner: Any = None): super().__init__(f"Bad file: {filename}.", inner) @@ -104,16 +92,6 @@ def __init__(self, input: str, message: str): super().__init__(f"Bad input [{input}]: {message}") -class BadAddressFormatError(KnownError): - def __init__(self, value: str): - super().__init__(f"Bad address [{value}].") - - -class EmptyAddressError(KnownError): - def __init__(self): - super().__init__("Address is empty.") - - class ExternalProcessError(KnownError): def __init__(self, command_line: str, message: str): super().__init__(f"""External process error: @@ -136,25 +114,6 @@ def __init__(self, name: str): super().__init__(f"This configuration name is protected: {name}.") -class UnsupportedConfigurationValue(KnownError): - pass - - -class UnknownDerivationFunction(KnownError): - def __init__(self): - super().__init__("Unknown key derivation function.") - - -class UnknownCipher(KnownError): - def __init__(self, name: str): - super().__init__(f"Unknown cipher: {name}.") - - -class GasLimitTooLarge(KnownError): - def __init__(self, current: int, limit: int): - super().__init__(f"The gas limit provided ({current}) exceeds the max gas limit of allowed for a transaction ({limit})") - - class BadUserInput(KnownError): def __init__(self, message: str): super().__init__(f"Bad user input: {message}.") @@ -213,3 +172,8 @@ def __init__(self, message: str, url: str, data: str, code: str): class WalletGenerationError(KnownError): def __init__(self, message: str): super().__init__(message) + + +class QueryContractError(KnownError): + def __init__(self, message: str, inner: Any = None): + super().__init__(message, str(inner)) diff --git a/multiversx_sdk_cli/tests/test_cli_contracts.py b/multiversx_sdk_cli/tests/test_cli_contracts.py index acaea1da..12e4c523 100644 --- a/multiversx_sdk_cli/tests/test_cli_contracts.py +++ b/multiversx_sdk_cli/tests/test_cli_contracts.py @@ -223,20 +223,19 @@ def test_contract_flow(capsys: Any): capsys.readouterr() main([ - "contract", "query", - contract, + "contract", "query", contract, "--function", "getSum", "--proxy", "https://testnet-api.multiversx.com" ]) response = get_query_response(capsys) - assert response == "" + assert len(response) == 1 + assert response == [""] # Clear the captured content capsys.readouterr() main([ - "contract", "call", - contract, + "contract", "call", contract, "--pem", alice, "--function", "add", "--recall-nonce", @@ -250,20 +249,19 @@ def test_contract_flow(capsys: Any): capsys.readouterr() main([ - "contract", "query", - contract, + "contract", "query", contract, "--function", "getSum", "--proxy", "https://testnet-api.multiversx.com" ]) response = get_query_response(capsys) - assert response["number"] == 7 + assert len(response) == 1 + assert response == ["07"] # Clear the captured content capsys.readouterr() main([ - "contract", "upgrade", - contract, + "contract", "upgrade", contract, "--bytecode", adder, "--pem", alice, "--recall-nonce", @@ -395,6 +393,101 @@ def test_contract_upgrade_with_abi(capsys: Any): assert data == "proposeSCUpgradeFromSource@000000000000000005000a14b9cb3f346116ded6802f2eb0235a36b2997569e1@@00000000000000000500ed8e25a94efa837aae0e593112cfbb01b448755069e1@0500@" +def test_contract_query(capsys: Any): + alice = f"{parent}/testdata/alice.pem" + adder = f"{parent}/testdata/adder.wasm" + adder_abi = f"{parent}/testdata/adder.abi.json" + + return_code = main([ + "contract", "deploy", + "--bytecode", adder, + "--abi", adder_abi, + "--pem", alice, + "--recall-nonce", + "--gas-limit", "5000000", + "--proxy", "https://testnet-api.multiversx.com", + "--arguments", "0", + "--send", "--wait-result" + ]) + assert not return_code + contract = get_contract_address(capsys) + + # Clear the captured content + capsys.readouterr() + + return_code = main([ + "contract", "call", contract, + "--pem", alice, + "--function", "add", + "--recall-nonce", + "--gas-limit", "5000000", + "--proxy", "https://testnet-api.multiversx.com", + "--arguments", "14", + "--send", "--wait-result" + ]) + assert not return_code + + # Clear the captured content + capsys.readouterr() + + # invalid, without abi + return_code = main([ + "contract", "query", contract, + "--function", "getSummm", + "--proxy", "https://testnet-api.multiversx.com" + ]) + assert return_code + output = _read_stdout(capsys) + if "invalid function (not found)" in output: + assert True + else: + assert False + + # Clear the captured content + capsys.readouterr() + + # invalid, with abi, error is thrown by sdk-py + return_code = main([ + "contract", "query", contract, + "--function", "getSummm", + "--abi", adder_abi, + "--proxy", "https://testnet-api.multiversx.com" + ]) + assert return_code + output = _read_stdout(capsys) + if "endpoint 'getSummm' not found" in output: + assert True + else: + assert False + + # Clear the captured content + capsys.readouterr() + + # query contract, without abi + return_code = main([ + "contract", "query", contract, + "--function", "getSum", + "--proxy", "https://testnet-api.multiversx.com" + ]) + assert not return_code + response = get_query_response(capsys) + assert response == ["0e"] + + # Clear the captured content + capsys.readouterr() + + # query contract, without abi + return_code = main([ + "contract", "query", contract, + "--function", "getSum", + "--abi", adder_abi, + "--proxy", "https://testnet-api.multiversx.com" + ]) + assert not return_code + response = get_query_response(capsys) + assert response == [14] + + def _read_stdout(capsys: Any) -> str: return capsys.readouterr().out.strip() @@ -407,7 +500,7 @@ def get_contract_address(capsys: Any): def get_query_response(capsys: Any): out = _read_stdout(capsys).replace("\n", "").replace(" ", "") - return json.loads(out)[0] + return json.loads(out) def get_transaction_data(capsys: Any) -> str: diff --git a/multiversx_sdk_cli/tests/test_contracts.py b/multiversx_sdk_cli/tests/test_contracts.py index d44bb3f9..38f214ce 100644 --- a/multiversx_sdk_cli/tests/test_contracts.py +++ b/multiversx_sdk_cli/tests/test_contracts.py @@ -8,9 +8,7 @@ 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 (SmartContract, - _interpret_as_number_if_safely, - _prepare_argument) +from multiversx_sdk_cli.contracts import SmartContract, _prepare_argument logging.basicConfig(level=logging.INFO) @@ -63,12 +61,6 @@ def test_contract_verification_create_request_signature(): assert signature.hex() == "30111258cc42ea08e0c6a3e053cc7086a88d614b8b119a244904e9a19896c73295b2fe5c520a1cb07cfe20f687deef9f294a0a05071e85c78a70a448ea5f0605" -def test_interpret_as_number_if_safely(): - assert _interpret_as_number_if_safely("") == 0 - assert _interpret_as_number_if_safely("0x5") == 5 - assert _interpret_as_number_if_safely("FF") == 255 - - def test_prepare_args_for_factories(): sc = SmartContract(TransactionsFactoryConfig("mock")) args = [ @@ -77,10 +69,10 @@ def test_prepare_args_for_factories(): "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" ] - arguments = sc.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 - assert arguments[3] == True + assert arguments[2] is False + assert arguments[3] is True assert arguments[4] == "test-string" assert arguments[5].to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" diff --git a/multiversx_sdk_cli/tests/test_shared.py b/multiversx_sdk_cli/tests/test_shared.py index 5df6c58f..caa43b3c 100644 --- a/multiversx_sdk_cli/tests/test_shared.py +++ b/multiversx_sdk_cli/tests/test_shared.py @@ -19,7 +19,7 @@ def test_prepare_chain_id_in_args(): prepare_chain_id_in_args(args) args.chain = "I" - args.proxy = "https://testnet-gateway.multiversx.com" + args.proxy = "https://testnet-api.multiversx.com" prepare_chain_id_in_args(args) assert args.chain == "T" diff --git a/multiversx_sdk_cli/tests/testdata/adder.abi.json b/multiversx_sdk_cli/tests/testdata/adder.abi.json new file mode 100644 index 00000000..56bbedc0 --- /dev/null +++ b/multiversx_sdk_cli/tests/testdata/adder.abi.json @@ -0,0 +1,72 @@ +{ + "buildInfo": { + "rustc": { + "version": "1.79.0", + "commitHash": "129f3b9964af4d4a709d1383930ade12dfe7c081", + "commitDate": "2024-06-10", + "channel": "Stable", + "short": "rustc 1.79.0 (129f3b996 2024-06-10)" + }, + "contractCrate": { + "name": "adder", + "version": "0.0.0", + "gitVersion": "v0.45.2.1-reproducible-337-g5478c6e" + }, + "framework": { + "name": "multiversx-sc", + "version": "0.51.1" + } + }, + "docs": [ + "One of the simplest smart contracts possible,", + "it holds a single variable in storage, which anyone can increment." + ], + "name": "Adder", + "constructor": { + "inputs": [ + { + "name": "initial_value", + "type": "BigUint" + } + ], + "outputs": [] + }, + "upgradeConstructor": { + "inputs": [ + { + "name": "initial_value", + "type": "BigUint" + } + ], + "outputs": [] + }, + "endpoints": [ + { + "name": "getSum", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "docs": [ + "Add desired amount to the storage variable." + ], + "name": "add", + "mutability": "mutable", + "inputs": [ + { + "name": "value", + "type": "BigUint" + } + ], + "outputs": [] + } + ], + "esdtAttributes": [], + "hasCallback": false, + "types": {} +} diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index 65f53cc8..9c08e748 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -212,7 +212,7 @@ def tx_to_dictionary_as_inner_for_relayed_V1(tx: Transaction) -> Dict[str, Any]: dictionary["sndUserName"] = base64.b64encode(tx.sender_username.encode()).decode() if tx.receiver_username: - dictionary[f"rcvUserName"] = base64.b64encode(tx.receiver_username.encode()).decode() + dictionary["rcvUserName"] = base64.b64encode(tx.receiver_username.encode()).decode() return dictionary diff --git a/multiversx_sdk_cli/utils.py b/multiversx_sdk_cli/utils.py index a21596d4..a61e41fa 100644 --- a/multiversx_sdk_cli/utils.py +++ b/multiversx_sdk_cli/utils.py @@ -37,9 +37,11 @@ def to_json(self): class BasicEncoder(json.JSONEncoder): - def default(self, o: Any): + def default(self, o: Any) -> Any: if isinstance(o, ISerializable): return o.to_dictionary() + if isinstance(o, bytes): + return o.hex() return super().default(o)