diff --git a/.github/workflows/release.yml b/.github/release.yml similarity index 100% rename from .github/workflows/release.yml rename to .github/release.yml diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 5b34d106..5d6d1121 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -3,7 +3,8 @@ from pathlib import Path from typing import Any, List -from multiversx_sdk_core import Address, Transaction +from multiversx_sdk_core import Address, AddressComputer, Transaction +from multiversx_sdk_core.transaction_factories import TransactionsFactoryConfig from multiversx_sdk_network_providers.proxy_network_provider import \ ProxyNetworkProvider @@ -11,12 +12,14 @@ from multiversx_sdk_cli.accounts import Account, LedgerAccount from multiversx_sdk_cli.cli_output import CLIOutputBuilder from multiversx_sdk_cli.cli_password import load_password +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 CodeMetadata, SmartContract +from multiversx_sdk_cli.contracts import SmartContract, query_contract from multiversx_sdk_cli.cosign_transaction import cosign_transaction from multiversx_sdk_cli.docker import is_docker_installed, run_docker from multiversx_sdk_cli.errors import DockerMissingError, NoWalletProvided +from multiversx_sdk_cli.interfaces import IAddress from multiversx_sdk_cli.projects.core import get_project_paths_recursively from multiversx_sdk_cli.projects.templates import Contract from multiversx_sdk_cli.ux import show_message @@ -299,32 +302,22 @@ def deploy(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - arguments = args.arguments - gas_price = args.gas_price - gas_limit = args.gas_limit - value = args.value - version = args.version - - contract = _prepare_contract(args) sender = _prepare_sender(args) cli_shared.prepare_chain_id_in_args(args) - tx = contract.deploy(sender, arguments, gas_price, gas_limit, value, args.chain, version, args.guardian, args.options) - tx = _sign_guarded_tx(args, tx) - - logger.info("Contract address: %s", contract.address.to_bech32()) - utils.log_explorer_contract_address(args.chain, contract.address.to_bech32()) + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config) - _send_or_simulate(tx, contract, args) + address_computer = AddressComputer(NUMBER_OF_SHARDS) + contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=sender.nonce) + tx = contract.get_deploy_transaction(sender, args) + tx = _sign_guarded_tx(args, tx) -def _prepare_contract(args: Any) -> SmartContract: - bytecode = utils.read_binary_file(Path(args.bytecode)).hex() + logger.info("Contract address: %s", contract_address.to_bech32()) + utils.log_explorer_contract_address(args.chain, contract_address.to_bech32()) - metadata = CodeMetadata(upgradeable=args.metadata_upgradeable, readable=args.metadata_readable, - payable=args.metadata_payable, payable_by_sc=args.metadata_payable_by_sc) - contract = SmartContract(bytecode=bytecode, metadata=metadata) - return contract + _send_or_simulate(tx, contract_address, args) def _prepare_sender(args: Any) -> Account: @@ -383,61 +376,57 @@ def call(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - contract_address = args.contract - function = args.function - arguments = args.arguments - gas_price = args.gas_price - gas_limit = args.gas_limit - value = args.value - version = args.version - - contract = SmartContract(Address.from_bech32(contract_address)) - sender = _prepare_sender(args) cli_shared.prepare_chain_id_in_args(args) + sender = _prepare_sender(args) + + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config) + contract_address = Address.new_from_bech32(args.contract) - tx = contract.execute(sender, function, arguments, gas_price, gas_limit, value, args.chain, version, args.guardian, args.options) + tx = contract.get_execute_transaction(sender, args) tx = _sign_guarded_tx(args, tx) - _send_or_simulate(tx, contract, args) + _send_or_simulate(tx, contract_address, args) def upgrade(args: Any): logger.debug("upgrade") + cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - contract_address = args.contract - arguments = args.arguments - gas_price = args.gas_price - gas_limit = args.gas_limit - value = args.value - version = args.version - - contract = _prepare_contract(args) - contract.address = Address.from_bech32(contract_address) - sender = _prepare_sender(args) cli_shared.prepare_chain_id_in_args(args) + sender = _prepare_sender(args) - tx = contract.upgrade(sender, arguments, gas_price, gas_limit, value, args.chain, version, args.guardian, args.options) + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config) + contract_address = Address.new_from_bech32(args.contract) + + tx = contract.get_upgrade_transaction(sender, args) tx = _sign_guarded_tx(args, tx) - _send_or_simulate(tx, contract, args) + _send_or_simulate(tx, contract_address, args) def query(args: Any): logger.debug("query") - contract_address = args.contract + # workaround so we can use the function bellow + args.chain = "" + cli_shared.prepare_chain_id_in_args(args) + + contract_address = Address.new_from_bech32(args.contract) + + proxy = ProxyNetworkProvider(args.proxy) function = args.function - arguments = args.arguments + arguments = args.arguments or [] - contract = SmartContract(address=Address.from_bech32(contract_address)) - result = contract.query(ProxyNetworkProvider(args.proxy), function, arguments) + result = query_contract(contract_address, proxy, function, arguments) utils.dump_out_json(result) -def _send_or_simulate(tx: Transaction, contract: SmartContract, args: Any): +def _send_or_simulate(tx: Transaction, contract_address: IAddress, args: Any): output_builder = cli_shared.send_or_simulate(tx, args, dump_output=False) - output_builder.set_contract_address(contract.address) + output_builder.set_contract_address(contract_address) utils.dump_out_json(output_builder.build(), outfile=args.outfile) diff --git a/multiversx_sdk_cli/cli_output.py b/multiversx_sdk_cli/cli_output.py index e56a3758..91560bfd 100644 --- a/multiversx_sdk_cli/cli_output.py +++ b/multiversx_sdk_cli/cli_output.py @@ -3,12 +3,11 @@ from collections import OrderedDict from typing import Any, Dict, List, Optional, Union -from multiversx_sdk_core import Address from multiversx_sdk_network_providers.transactions import \ transaction_to_dictionary from multiversx_sdk_cli import utils -from multiversx_sdk_cli.interfaces import ITransaction +from multiversx_sdk_cli.interfaces import IAddress, ITransaction from multiversx_sdk_cli.utils import ISerializable logger = logging.getLogger("cli.output") @@ -19,7 +18,7 @@ def __init__(self) -> None: self.emitted_transaction_hash: Optional[str] = None self.emitted_transaction: Union[ITransaction, None] = None self.emitted_transaction_omitted_fields: List[str] = [] - self.contract_address: Union[Address, None] = None + self.contract_address: Union[IAddress, None] = None self.transaction_on_network: Union[ISerializable, None] = None self.transaction_on_network_omitted_fields: List[str] = [] self.simulation_results: Union[ISerializable, None] = None @@ -33,7 +32,7 @@ def set_emitted_transaction(self, emitted_transaction: ITransaction, omitted_fie self.emitted_transaction_omitted_fields = omitted_fields return self - def set_contract_address(self, contract_address: Address): + def set_contract_address(self, contract_address: IAddress): self.contract_address = contract_address return self diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index 1a7f970f..46c00519 100644 --- a/multiversx_sdk_cli/cli_shared.py +++ b/multiversx_sdk_cli/cli_shared.py @@ -93,9 +93,9 @@ def add_tx_args(args: List[str], sub: Any, with_nonce: bool = True, with_receive def add_guardian_args(sub: Any): - sub.add_argument("--guardian", type=str, help="the address of the guradian") - sub.add_argument("--guardian-service-url", type=str, help="the url of the guardian service") - sub.add_argument("--guardian-2fa-code", type=str, help="the 2fa code for the guardian") + sub.add_argument("--guardian", type=str, help="the address of the guradian", default="") + sub.add_argument("--guardian-service-url", type=str, help="the url of the guardian service", default="") + sub.add_argument("--guardian-2fa-code", type=str, help="the 2fa code for the guardian", default="") def add_wallet_args(args: List[str], sub: Any): diff --git a/multiversx_sdk_cli/constants.py b/multiversx_sdk_cli/constants.py index 0e92e53a..fabd8809 100644 --- a/multiversx_sdk_cli/constants.py +++ b/multiversx_sdk_cli/constants.py @@ -11,3 +11,5 @@ DEFAULT_HRP = "erd" ADDRESS_ZERO_BECH32 = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu" + +NUMBER_OF_SHARDS = 3 diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 6575ce77..634cbdcb 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -1,14 +1,17 @@ import base64 import logging -from typing import Any, List, Optional, Protocol, Sequence, Tuple +from pathlib import Path +from typing import Any, List, Optional, Protocol, Sequence -from multiversx_sdk_core import Transaction, TransactionPayload -from multiversx_sdk_core.address import Address, AddressComputer +from multiversx_sdk_core import TokenComputer, Transaction, TransactionPayload +from multiversx_sdk_core.address import Address +from multiversx_sdk_core.transaction_factories import \ + SmartContractTransactionsFactory from multiversx_sdk_network_providers.interface import IAddress, IContractQuery -from multiversx_sdk_cli import config, constants, errors -from multiversx_sdk_cli.accounts import Account, EmptyAddress -from multiversx_sdk_cli.constants import ADDRESS_ZERO_BECH32, DEFAULT_HRP +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.utils import Object logger = logging.getLogger("contracts") @@ -61,161 +64,130 @@ class IContractQueryResponse(Protocol): return_message: str -class SmartContract: - def __init__(self, address: Optional[IAddress] = EmptyAddress(), bytecode=None, metadata=None): - self.address = address - self.bytecode = bytecode - self.metadata = metadata or CodeMetadata() - - def deploy(self, owner: Account, arguments: List[Any], gas_price: int, gas_limit: int, value: int, chain: str, version: int, guardian: str, options: int) -> Transaction: - self.owner = owner - address_computer = AddressComputer(number_of_shards=3) - self.address = address_computer.compute_contract_address(self.owner.address, self.owner.nonce) - - arguments = arguments or [] - gas_price = int(gas_price) - gas_limit = int(gas_limit) - value = value or 0 - - tx = Transaction( - chain_id=chain, - sender=owner.address.to_bech32(), - receiver=ADDRESS_ZERO_BECH32, - gas_limit=gas_limit, - gas_price=gas_price, - nonce=owner.nonce, - amount=value, - data=self.prepare_deploy_transaction_data(arguments).data, - version=version, - options=options - ) +class IConfig(Protocol): + chain_id: str + min_gas_limit: int + gas_limit_per_byte: int - if guardian: - tx.guardian = guardian +class SmartContract: + def __init__(self, config: IConfig): + self._factory = SmartContractTransactionsFactory(config, TokenComputer()) + + def get_deploy_transaction(self, owner: Account, args: Any) -> Transaction: + arguments = args.arguments or [] + arguments = prepare_args_for_factory(arguments) + + tx = self._factory.create_transaction_for_deploy( + sender=owner.address, + bytecode=Path(args.bytecode), + gas_limit=int(args.gas_limit), + arguments=arguments, + native_transfer_amount=int(args.value), + is_upgradeable=args.metadata_upgradeable, + is_readable=args.metadata_readable, + is_payable=args.metadata_payable, + is_payable_by_sc=args.metadata_payable_by_sc + ) + tx.nonce = owner.nonce + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - return tx - def prepare_deploy_transaction_data(self, arguments: List[Any]) -> TransactionPayload: - tx_data = f"{self.bytecode}@{constants.VM_TYPE_WASM_VM}@{self.metadata.to_hex()}" - - for arg in arguments: - tx_data += f"@{_prepare_argument(arg)}" - - return TransactionPayload.from_str(tx_data) - - def execute(self, caller: Account, function: str, arguments: List[str], gas_price: int, gas_limit: int, value: int, chain: str, version: int, guardian: str, options: int) -> Transaction: - self.caller = caller + return tx - arguments = arguments or [] - gas_price = int(gas_price) - gas_limit = int(gas_limit) - value = value or 0 - receiver = self.address if self.address else EmptyAddress() - - tx = Transaction( - chain_id=chain, - sender=caller.address.to_bech32(), - receiver=receiver.to_bech32(), - gas_limit=gas_limit, - gas_price=gas_price, - nonce=caller.nonce, - amount=value, - data=self.prepare_execute_transaction_data(function, arguments).data, - version=version, - options=options + def get_execute_transaction(self, caller: Account, args: Any) -> Transaction: + contract_address = Address.new_from_bech32(args.contract) + arguments = args.arguments or [] + arguments = prepare_args_for_factory(arguments) + + tx = self._factory.create_transaction_for_execute( + sender=caller.address, + contract=contract_address, + function=args.function, + gas_limit=int(args.gas_limit), + arguments=arguments, + native_transfer_amount=int(args.value), + token_transfers=[] ) - - if guardian: - tx.guardian = guardian - + tx.nonce = caller.nonce + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian tx.signature = bytes.fromhex(caller.sign_transaction(tx)) + return tx - def prepare_execute_transaction_data(self, function: str, arguments: List[Any]) -> TransactionPayload: - tx_data = function - - for arg in arguments: - tx_data += f"@{_prepare_argument(arg)}" - - return TransactionPayload.from_str(tx_data) - - def upgrade(self, owner: Account, arguments: List[Any], gas_price: int, gas_limit: int, value: int, chain: str, version: int, guardian: str, options: int) -> Transaction: - self.owner = owner - - arguments = arguments or [] - gas_price = int(gas_price or config.DEFAULT_GAS_PRICE) - gas_limit = int(gas_limit) - value = value or 0 - receiver = self.address if self.address else EmptyAddress() - - tx = Transaction( - chain_id=chain, - sender=owner.address.to_bech32(), - receiver=receiver.to_bech32(), - gas_limit=gas_limit, - gas_price=gas_price, - nonce=owner.nonce, - amount=value, - data=self.prepare_upgrade_transaction_data(arguments).data, - version=version, - options=options + def get_upgrade_transaction(self, owner: Account, args: Any): + contract_address = Address.new_from_bech32(args.contract) + arguments = args.arguments or [] + arguments = prepare_args_for_factory(arguments) + + tx = self._factory.create_transaction_for_upgrade( + sender=owner.address, + contract=contract_address, + bytecode=Path(args.bytecode), + gas_limit=int(args.gas_limit), + arguments=arguments, + native_transfer_amount=int(args.value), + is_upgradeable=args.metadata_upgradeable, + is_readable=args.metadata_readable, + is_payable=args.metadata_payable, + is_payable_by_sc=args.metadata_payable_by_sc ) - - if guardian: - tx.guardian = guardian - + tx.nonce = owner.nonce + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - return tx - def prepare_upgrade_transaction_data(self, arguments: List[Any]) -> TransactionPayload: - tx_data = f"upgradeContract@{self.bytecode}@{self.metadata.to_hex()}" + return tx - for arg in arguments: - tx_data += f"@{_prepare_argument(arg)}" - return TransactionPayload.from_str(tx_data) +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( - self, - proxy: INetworkProvider, - function: str, - arguments: List[Any], - value: int = 0, - caller: Optional[Address] = None - ) -> List[Any]: - response_data = self.query_detailed(proxy, function, arguments, value, caller) - return_data = response_data.return_data - return [self._interpret_return_data(data) for data in return_data] - def query_detailed(self, proxy: INetworkProvider, function: str, arguments: List[Any], - value: int = 0, caller: Optional[Address] = None) -> Any: - arguments = arguments or [] - # Temporary workaround, until we use sdk-core's serializer. - prepared_arguments = [bytes.fromhex(_prepare_argument(arg)) for arg in arguments] +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(self.address, function, value, prepared_arguments, caller) + 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 + 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(self, 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) +def _interpret_return_data(data: str) -> Any: + if not data: + return data - result = QueryResult(data, as_hex, as_number) - return result - except Exception: - logger.warn(f"Cannot interpret return data: {data}") - return None + 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]: @@ -232,6 +204,43 @@ def _interpret_as_number_if_safely(as_hex: str) -> Optional[int]: return None +def prepare_execute_transaction_data(function: str, arguments: List[Any]) -> TransactionPayload: + tx_data = function + + for arg in arguments: + tx_data += f"@{_prepare_argument(arg)}" + + 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):]) + + return args + + +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 def _prepare_argument(argument: Any): as_str = str(argument) as_hex = _to_hex(as_str) @@ -286,29 +295,3 @@ def ensure_even_length(string: str) -> str: if len(string) % 2 == 1: return '0' + string return string - - -def sum_flag_values(flag_value_pairs: List[Tuple[int, bool]]) -> int: - value_sum = 0 - for value, flag in flag_value_pairs: - if flag: - value_sum += value - return value_sum - - -class CodeMetadata: - def __init__(self, upgradeable: bool = True, readable: bool = True, payable: bool = False, payable_by_sc: bool = False): - self.upgradeable = upgradeable - self.readable = readable - self.payable = payable - self.payable_by_sc = payable_by_sc - - def to_hex(self): - flag_value_pairs = [ - (0x01_00, self.upgradeable), - (0x04_00, self.readable), - (0x00_02, self.payable), - (0x00_04, self.payable_by_sc) - ] - metadata_value = sum_flag_values(flag_value_pairs) - return f"{metadata_value:04X}" diff --git a/multiversx_sdk_cli/dns.py b/multiversx_sdk_cli/dns.py index 1485532a..aca2389d 100644 --- a/multiversx_sdk_cli/dns.py +++ b/multiversx_sdk_cli/dns.py @@ -1,12 +1,12 @@ from typing import Any, List, Protocol from Cryptodome.Hash import keccak -from multiversx_sdk_core.address import Address, AddressComputer +from multiversx_sdk_core import Address, AddressComputer 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 SmartContract +from multiversx_sdk_cli.contracts import query_contract from multiversx_sdk_cli.transactions import ( do_prepare_transaction, tx_to_dictionary_as_inner_for_relayed_V1) @@ -23,8 +23,8 @@ def query_contract(self, query: Any) -> Any: def resolve(name: str, proxy: INetworkProvider) -> Address: name_arg = "0x{}".format(str.encode(name).hex()) dns_address = dns_address_for_name(name) - contract = SmartContract(dns_address) - result = contract.query(proxy, "resolve", [name_arg]) + + result = query_contract(dns_address, proxy, "resolve", [name_arg]) if len(result) == 0: return Address.from_bech32(ADDRESS_ZERO_BECH32) return Address.from_hex(result[0].hex, DEFAULT_HRP) @@ -33,8 +33,8 @@ def resolve(name: str, proxy: INetworkProvider) -> Address: 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) - contract = SmartContract(dns_address) - contract.query(proxy, "validateName", [name_arg]) + + query_contract(dns_address, proxy, "validateName", [name_arg]) def register(args: Any): @@ -69,8 +69,7 @@ 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) - contract = SmartContract(dns_address) - result = contract.query(proxy, "getRegistrationCost", []) + result = query_contract(dns_address, proxy, "getRegistrationCost", []) if len(result[0]) == 0: return 0 else: @@ -79,8 +78,7 @@ def registration_cost(shard_id: int, proxy: INetworkProvider) -> int: def version(shard_id: int, proxy: INetworkProvider) -> str: dns_address = compute_dns_address_for_shard_id(shard_id) - contract = SmartContract(dns_address) - result = contract.query(proxy, "version", []) + result = query_contract(dns_address, proxy, "version", []) return bytearray.fromhex(result[0].hex).decode() diff --git a/multiversx_sdk_cli/localnet/genesis.py b/multiversx_sdk_cli/localnet/genesis.py index 90a3e3c6..35f1e37b 100644 --- a/multiversx_sdk_cli/localnet/genesis.py +++ b/multiversx_sdk_cli/localnet/genesis.py @@ -1,6 +1,6 @@ from multiversx_sdk_core.address import Address, AddressComputer -from multiversx_sdk_cli.contracts import SmartContract +from multiversx_sdk_cli.constants import NUMBER_OF_SHARDS from multiversx_sdk_cli.localnet import wallets @@ -10,13 +10,12 @@ def get_owner_of_genesis_contracts(): def get_delegation_address() -> Address: - contract = SmartContract() - contract.owner = get_owner_of_genesis_contracts() - contract.owner.nonce = 0 + owner = get_owner_of_genesis_contracts() + owner.nonce = 0 - address_computer = AddressComputer() - contract.address = address_computer.compute_contract_address(contract.owner.address, contract.owner.nonce) - return contract.address + address_computer = AddressComputer(NUMBER_OF_SHARDS) + address = address_computer.compute_contract_address(owner.address, owner.nonce) + return address def is_last_user(nickname: str) -> bool: diff --git a/multiversx_sdk_cli/tests/test_cli_contracts.py b/multiversx_sdk_cli/tests/test_cli_contracts.py index 259b1a0e..f8e27c6e 100644 --- a/multiversx_sdk_cli/tests/test_cli_contracts.py +++ b/multiversx_sdk_cli/tests/test_cli_contracts.py @@ -1,3 +1,4 @@ +import json from pathlib import Path from typing import Any @@ -165,3 +166,90 @@ def test_contract_call(): ] ) assert Path.is_file(output_file) == True + + +def test_contract_flow(capsys: Any): + alice = f"{parent}/testdata/alice.pem" + adder = f"{parent}/testdata/adder.wasm" + + main([ + "contract", "deploy", + "--bytecode", adder, + "--pem", alice, + "--recall-nonce", + "--gas-limit", "5000000", + "--proxy", "https://testnet-api.multiversx.com", + "--arguments", "0", + "--send", "--wait-result" + ]) + contract = get_contract_address(capsys) + + # Clear the captured content + capsys.readouterr() + + main([ + "contract", "query", + contract, + "--function", "getSum", + "--proxy", "https://testnet-api.multiversx.com" + ]) + response = get_query_response(capsys) + assert response == "" + + # Clear the captured content + capsys.readouterr() + + main([ + "contract", "call", + contract, + "--pem", alice, + "--function", "add", + "--recall-nonce", + "--gas-limit", "5000000", + "--proxy", "https://testnet-api.multiversx.com", + "--arguments", "7", + "--send", "--wait-result" + ]) + + # Clear the captured content + capsys.readouterr() + + main([ + "contract", "query", + contract, + "--function", "getSum", + "--proxy", "https://testnet-api.multiversx.com" + ]) + response = get_query_response(capsys) + assert response["number"] == 7 + + # Clear the captured content + capsys.readouterr() + + main([ + "contract", "upgrade", + contract, + "--bytecode", adder, + "--pem", alice, + "--recall-nonce", + "--gas-limit", "5000000", + "--proxy", "https://testnet-api.multiversx.com", + "--arguments", "0", + "--send", "--wait-result" + ]) + + +def _read_stdout(capsys: Any) -> str: + return capsys.readouterr().out.strip() + + +def get_contract_address(capsys: Any): + out = _read_stdout(capsys) + output = json.loads(out) + return output["contractAddress"] + + +def get_query_response(capsys: Any): + out = _read_stdout(capsys).replace("\n", "").replace(" ", "") + print(out) + return json.loads(out)[0] diff --git a/multiversx_sdk_cli/tests/test_code_metadata.py b/multiversx_sdk_cli/tests/test_code_metadata.py deleted file mode 100644 index 2cf904a8..00000000 --- a/multiversx_sdk_cli/tests/test_code_metadata.py +++ /dev/null @@ -1,21 +0,0 @@ -from multiversx_sdk_cli.contracts import CodeMetadata - - -def test_code_metadata_defaults(): - assert CodeMetadata().to_hex() == "0500" - assert CodeMetadata().to_hex() == CodeMetadata(upgradeable=True, readable=True, payable=False, payable_by_sc=False).to_hex() - - -def test_code_metadata_single_flag(): - assert CodeMetadata(upgradeable=False, readable=False, payable=False, payable_by_sc=False).to_hex() == "0000" - assert CodeMetadata(upgradeable=True, readable=False, payable=False, payable_by_sc=False).to_hex() == "0100" - assert CodeMetadata(upgradeable=False, readable=True, payable=False, payable_by_sc=False).to_hex() == "0400" - assert CodeMetadata(upgradeable=False, readable=False, payable=True, payable_by_sc=False).to_hex() == "0002" - assert CodeMetadata(upgradeable=False, readable=False, payable=False, payable_by_sc=True).to_hex() == "0004" - - -def test_code_metadata_multiple_flags(): - assert CodeMetadata(upgradeable=False, readable=True, payable=False, payable_by_sc=True).to_hex() == "0404" - assert CodeMetadata(upgradeable=True, readable=True, payable=False, payable_by_sc=False).to_hex() == "0500" - assert CodeMetadata(upgradeable=False, readable=False, payable=True, payable_by_sc=True).to_hex() == "0006" - assert CodeMetadata(upgradeable=True, readable=True, payable=True, payable_by_sc=True).to_hex() == "0506" diff --git a/multiversx_sdk_cli/tests/test_contracts.py b/multiversx_sdk_cli/tests/test_contracts.py index f6eed78d..733969a3 100644 --- a/multiversx_sdk_cli/tests/test_contracts.py +++ b/multiversx_sdk_cli/tests/test_contracts.py @@ -3,15 +3,14 @@ import pytest from Cryptodome.Hash import keccak -from multiversx_sdk_core.address import Address, AddressComputer +from multiversx_sdk_core.address import Address 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.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 (_interpret_as_number_if_safely, + _prepare_argument, + prepare_args_for_factory) logging.basicConfig(level=logging.INFO) @@ -23,23 +22,6 @@ def test_playground_keccak(): assert hexhash == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" -def test_compute_address(): - address_computer = AddressComputer() - contract = SmartContract() - contract.owner = Account(address=Address.from_hex("93ee6143cdc10ce79f15b2a6c2ad38e9b6021c72a1779051f47154fd54cfbd5e", DEFAULT_HRP)) - - contract.owner.nonce = 0 - contract.address = address_computer.compute_contract_address(contract.owner.address, contract.owner.nonce) - assert contract.address - assert contract.address.hex() == "00000000000000000500bb652200ed1f994200ab6699462cab4b1af7b11ebd5e" - assert contract.address.bech32() == "erd1qqqqqqqqqqqqqpgqhdjjyq8dr7v5yq9tv6v5vt9tfvd00vg7h40q6779zn" - - contract.owner.nonce = 1 - contract.address = address_computer.compute_contract_address(contract.owner.address, contract.owner.nonce) - assert contract.address.hex() == "000000000000000005006e4f90488e27342f9a46e1809452c85ee7186566bd5e" - assert contract.address.bech32() == "erd1qqqqqqqqqqqqqpgqde8eqjywyu6zlxjxuxqfg5kgtmn3setxh40qen8egy" - - def test_prepare_argument(): assert _prepare_argument('0x5') == '05' assert _prepare_argument('5') == '05' @@ -85,3 +67,19 @@ 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(): + args = [ + "0x5", "123", "false", "true", + "str:test-string", + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + ] + + arguments = prepare_args_for_factory(args) + assert arguments[0] == b"\x05" + assert arguments[1] == 123 + assert arguments[2] == False + assert arguments[3] == True + assert arguments[4] == "test-string" + assert arguments[5].to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" diff --git a/multiversx_sdk_cli/tests/testdata/adder.wasm b/multiversx_sdk_cli/tests/testdata/adder.wasm old mode 100755 new mode 100644 index 820c0f40..ffb39b0d Binary files a/multiversx_sdk_cli/tests/testdata/adder.wasm and b/multiversx_sdk_cli/tests/testdata/adder.wasm differ diff --git a/multiversx_sdk_cli/validators/core.py b/multiversx_sdk_cli/validators/core.py index b19a21ca..61d0c136 100644 --- a/multiversx_sdk_cli/validators/core.py +++ b/multiversx_sdk_cli/validators/core.py @@ -11,7 +11,7 @@ from multiversx_sdk_cli.cli_password import load_password from multiversx_sdk_cli.config import (GAS_PER_DATA_BYTE, MIN_GAS_LIMIT, MetaChainSystemSCsCost) -from multiversx_sdk_cli.contracts import SmartContract +from multiversx_sdk_cli.contracts import prepare_execute_transaction_data from multiversx_sdk_cli.errors import BadUsage from multiversx_sdk_cli.validators.validators_file import ValidatorsFile @@ -68,7 +68,7 @@ def prepare_transaction_data_for_stake(node_operator_address: Address, validator if reward_address: call_arguments.append(f"0x{reward_address.to_hex()}") - data = SmartContract().prepare_execute_transaction_data("stake", call_arguments) + 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 diff --git a/pyproject.toml b/pyproject.toml index 979d699f..7ea4ae28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,9 +27,9 @@ dependencies = [ "semver", "requests-cache", "rich==13.3.4", - "multiversx-sdk-network-providers<0.13.0", - "multiversx-sdk-wallet<0.9.0,", - "multiversx-sdk-core<0.8.0" + "multiversx-sdk-network-providers>=0.12.0,<0.13.0", + "multiversx-sdk-wallet>=0.8.0,<0.9.0,", + "multiversx-sdk-core>=0.7.0,<0.8.0" ] [tool.hatch.build] diff --git a/requirements.txt b/requirements.txt index ea54e91b..028aa23d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,6 @@ semver requests-cache rich==13.3.4 -multiversx-sdk-core<0.8.0 -multiversx-sdk-network-providers<0.13.0 -multiversx-sdk-wallet<0.9.0 +multiversx-sdk-core>=0.7.0,<0.8.0 +multiversx-sdk-network-providers>=0.12.0,<0.13.0 +multiversx-sdk-wallet>=0.8.0,<0.9.0