diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index ac721279..a3260bff 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -2,7 +2,7 @@ import logging import os from pathlib import Path -from typing import Any +from typing import Any, Union from multiversx_sdk import ( Address, @@ -21,8 +21,8 @@ from multiversx_sdk_cli.contracts import SmartContract 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.errors import DockerMissingError +from multiversx_sdk_cli.interfaces import IAccount from multiversx_sdk_cli.ux import show_warning logger = logging.getLogger("cli.contracts") @@ -32,15 +32,10 @@ def setup_parser(args: list[str], subparsers: Any) -> Any: parser = cli_shared.add_group_subparser( subparsers, "contract", - "Build, deploy, upgrade and interact with Smart Contracts", + "Deploy, upgrade and interact with Smart Contracts", ) subparsers = parser.add_subparsers() - sub = cli_shared.add_command_subparser( - subparsers, "contract", "build", "Build a Smart Contract project. This command is DISABLED." - ) - sub.set_defaults(func=build) - output_description = CLIOutputBuilder.describe( with_contract=True, with_transaction_on_network=True, with_simulation=True ) @@ -72,6 +67,7 @@ def setup_parser(args: list[str], subparsers: Any) -> Any: ) cli_shared.add_broadcast_args(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.set_defaults(func=deploy) @@ -103,6 +99,7 @@ def setup_parser(args: list[str], subparsers: Any) -> Any: ) cli_shared.add_broadcast_args(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.set_defaults(func=call) @@ -134,6 +131,7 @@ def setup_parser(args: list[str], subparsers: Any) -> Any: ) cli_shared.add_broadcast_args(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.set_defaults(func=upgrade) @@ -205,6 +203,11 @@ def setup_parser(args: list[str], subparsers: Any) -> Any: ) sub.set_defaults(func=do_reproducible_build) + sub = cli_shared.add_command_subparser( + subparsers, "contract", "build", "Build a Smart Contract project. This command is DISABLED." + ) + sub.set_defaults(func=build) + parser.epilog = cli_shared.build_group_epilog(subparsers) return subparsers @@ -274,7 +277,7 @@ def _add_contract_arg(sub: Any): def _add_contract_abi_arg(sub: Any): - sub.add_argument("--abi", type=str, help="the ABI of the Smart Contract") + sub.add_argument("--abi", type=str, help="the ABI file of the Smart Contract") def _add_function_arg(sub: Any): @@ -338,9 +341,20 @@ def deploy(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) cli_shared.prepare_chain_id_in_args(args) - cli_shared.prepare_nonce_in_args(args) sender = cli_shared.prepare_account(args) + + if not args.nonce: + 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) + config = TransactionsFactoryConfig(args.chain) abi = Abi.load(Path(args.abi)) if args.abi else None contract = SmartContract(config, abi) @@ -358,15 +372,17 @@ def deploy(args: Any): payable_by_sc=args.metadata_payable_by_sc, gas_limit=int(args.gas_limit), value=int(args.value), - nonce=int(args.nonce), + nonce=nonce, version=int(args.version), options=int(args.options), - guardian=args.guardian, + guardian=guardian_address, + relayer=relayer_address, ) - tx = _sign_guarded_tx(args, tx) + tx = _sign_guarded_tx_if_guardian(guardian, args, tx) + _sign_relayed_tx_if_relayer(relayer, tx) address_computer = AddressComputer(NUMBER_OF_SHARDS) - contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce) + contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=tx.nonce) logger.info("Contract address: %s", contract_address.to_bech32()) utils.log_explorer_contract_address(args.chain, contract_address.to_bech32()) @@ -374,29 +390,39 @@ def deploy(args: Any): _send_or_simulate(tx, contract_address, args) -def _sign_guarded_tx(args: Any, tx: Transaction) -> Transaction: - try: - guardian_account = cli_shared.prepare_guardian_account(args) - except NoWalletProvided: - guardian_account = None - - if guardian_account: - tx.guardian_signature = bytes.fromhex(guardian_account.sign_transaction(tx)) - elif args.guardian: - tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) # type: ignore +def _sign_guarded_tx_if_guardian(guardian: Union[IAccount, None], args: Any, tx: Transaction) -> Transaction: + if guardian: + tx.guardian_signature = bytes.fromhex(guardian.sign_transaction(tx)) + elif tx.guardian and args.guardian_service_url and args.guardian_2fa_code: + tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) return tx +def _sign_relayed_tx_if_relayer(relayer: Union[IAccount, None], tx: Transaction): + if relayer and tx.relayer: + tx.relayer_signature = bytes.fromhex(relayer.sign_transaction(tx)) + + def call(args: Any): logger.debug("call") cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) cli_shared.prepare_chain_id_in_args(args) - cli_shared.prepare_nonce_in_args(args) sender = cli_shared.prepare_account(args) + if not args.nonce: + 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) + config = TransactionsFactoryConfig(args.chain) abi = Abi.load(Path(args.abi)) if args.abi else None contract = SmartContract(config, abi) @@ -413,12 +439,14 @@ def call(args: Any): gas_limit=int(args.gas_limit), value=int(args.value), transfers=args.token_transfers, - nonce=int(args.nonce), + nonce=nonce, version=int(args.version), options=int(args.options), - guardian=args.guardian, + guardian=guardian_address, + relayer=relayer_address, ) - tx = _sign_guarded_tx(args, tx) + tx = _sign_guarded_tx_if_guardian(guardian, args, tx) + _sign_relayed_tx_if_relayer(relayer, tx) _send_or_simulate(tx, contract_address, args) @@ -428,9 +456,20 @@ def upgrade(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) cli_shared.prepare_chain_id_in_args(args) - cli_shared.prepare_nonce_in_args(args) sender = cli_shared.prepare_account(args) + + if not args.nonce: + 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) + config = TransactionsFactoryConfig(args.chain) abi = Abi.load(Path(args.abi)) if args.abi else None contract = SmartContract(config, abi) @@ -450,12 +489,14 @@ def upgrade(args: Any): payable_by_sc=args.metadata_payable_by_sc, gas_limit=int(args.gas_limit), value=int(args.value), - nonce=int(args.nonce), + nonce=nonce, version=int(args.version), options=int(args.options), - guardian=args.guardian, + guardian=guardian_address, + relayer=relayer_address, ) - tx = _sign_guarded_tx(args, tx) + tx = _sign_guarded_tx_if_guardian(guardian, args, tx) + _sign_relayed_tx_if_relayer(relayer, tx) _send_or_simulate(tx, contract_address, args) @@ -463,11 +504,8 @@ def upgrade(args: Any): def query(args: Any): logger.debug("query") - # workaround so we can use the function below to set chainID - args.chain = "" - cli_shared.prepare_chain_id_in_args(args) - - factory_config = TransactionsFactoryConfig(args.chain) + # we don't need chainID to query a contract; we use the provided proxy + factory_config = TransactionsFactoryConfig("") abi = Abi.load(Path(args.abi)) if args.abi else None contract = SmartContract(factory_config, abi) @@ -504,14 +542,14 @@ def _get_contract_arguments(args: Any) -> tuple[list[Any], bool]: return args.arguments, True -def _send_or_simulate(tx: Transaction, contract_address: IAddress, args: Any): +def _send_or_simulate(tx: Transaction, contract_address: Address, args: Any): output_builder = cli_shared.send_or_simulate(tx, args, dump_output=False) output_builder.set_contract_address(contract_address) utils.dump_out_json(output_builder.build(), outfile=args.outfile) def verify(args: Any) -> None: - contract = Address.from_bech32(args.contract) + contract = Address.new_from_bech32(args.contract) verifier_url = args.verifier_url packaged_src = Path(args.packaged_src).expanduser().resolve() diff --git a/multiversx_sdk_cli/cli_delegation.py b/multiversx_sdk_cli/cli_delegation.py index f5d409f7..c00c2869 100644 --- a/multiversx_sdk_cli/cli_delegation.py +++ b/multiversx_sdk_cli/cli_delegation.py @@ -359,6 +359,7 @@ def _add_common_arguments(args: List[str], sub: Any): cli_shared.add_broadcast_args(sub) cli_shared.add_outfile_arg(sub, what="signed transaction, hash") cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) def ensure_arguments_are_provided_and_prepared(args: Any): diff --git a/multiversx_sdk_cli/cli_dns.py b/multiversx_sdk_cli/cli_dns.py index e4be41c0..1e890b50 100644 --- a/multiversx_sdk_cli/cli_dns.py +++ b/multiversx_sdk_cli/cli_dns.py @@ -35,6 +35,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: cli_shared.add_proxy_arg(sub) cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.add_argument("--name", help="the name to register") sub.set_defaults(func=register) diff --git a/multiversx_sdk_cli/cli_password.py b/multiversx_sdk_cli/cli_password.py index d7fff56b..98666c31 100644 --- a/multiversx_sdk_cli/cli_password.py +++ b/multiversx_sdk_cli/cli_password.py @@ -14,3 +14,10 @@ def load_guardian_password(args: Any) -> str: with open(args.guardian_passfile) as pass_file: return pass_file.read().strip() return getpass("Keyfile's password: ") + + +def load_relayer_password(args: Any) -> str: + if args.relayer_passfile: + with open(args.relayer_passfile) as pass_file: + return pass_file.read().strip() + return getpass("Keyfile's password: ") diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index 443b3956..67f77c7d 100644 --- a/multiversx_sdk_cli/cli_shared.py +++ b/multiversx_sdk_cli/cli_shared.py @@ -3,14 +3,18 @@ import copy import sys from argparse import FileType -from typing import Any, Dict, List, Text, cast +from typing import Any, Dict, List, Text, Union, cast from multiversx_sdk import Address, ProxyNetworkProvider, Transaction from multiversx_sdk_cli import config, errors, utils from multiversx_sdk_cli.accounts import Account, LedgerAccount from multiversx_sdk_cli.cli_output import CLIOutputBuilder -from multiversx_sdk_cli.cli_password import load_guardian_password, load_password +from multiversx_sdk_cli.cli_password import ( + load_guardian_password, + load_password, + load_relayer_password, +) from multiversx_sdk_cli.constants import ( DEFAULT_TX_VERSION, TRANSACTION_OPTIONS_TX_GUARDED, @@ -69,7 +73,6 @@ def add_tx_args( with_receiver: bool = True, with_data: bool = True, with_estimate_gas: bool = False, - with_relayer_wallet_args: bool = True, ): if with_nonce: sub.add_argument( @@ -121,8 +124,6 @@ def add_tx_args( ) sub.add_argument("--relayer", help="the bech32 address of the relayer") - if with_relayer_wallet_args: - add_relayed_v3_wallet_args(args, sub) add_guardian_args(sub) @@ -360,6 +361,58 @@ def prepare_guardian_account(args: Any): return account +def load_guardian_account(args: Any) -> Union[Account, None]: + if args.guardian_pem: + return Account(pem_file=args.guardian_pem, pem_index=args.guardian_pem_index) + elif args.guardian_keyfile: + password = load_guardian_password(args) + return Account(key_file=args.guardian_keyfile, password=password) + elif args.guardian_ledger: + address = do_get_ledger_address( + account_index=args.guardian_ledger_account_index, + address_index=args.guardian_ledger_address_index, + ) + return Account(Address.new_from_bech32(address)) + + return None + + +def get_guardian_address(guardian: Union[Account, None], args: Any) -> Union[Address, None]: + if guardian: + return guardian.address + + if hasattr(args, "guardian") and args.guardian: + return Address.new_from_bech32(args.guardian) + + return None + + +def get_relayer_address(relayer: Union[Account, None], args: Any) -> Union[Address, None]: + if relayer: + return relayer.address + + if hasattr(args, "relayer") and args.relayer: + return Address.new_from_bech32(args.relayer) + + return None + + +def load_relayer_account(args: Any) -> Union[Account, None]: + if args.relayer_pem: + return Account(pem_file=args.relayer_pem, pem_index=args.relayer_pem_index) + elif args.relayer_keyfile: + password = load_relayer_password(args) + return Account(key_file=args.relayer_keyfile, password=password) + elif args.relayer_ledger: + address = do_get_ledger_address( + account_index=args.relayer_ledger_account_index, + address_index=args.relayer_ledger_address_index, + ) + return Account(Address.new_from_bech32(address)) + + return None + + def prepare_nonce_in_args(args: Any): if args.recall_nonce and not args.proxy: raise ArgumentsNotProvidedError("When using `--recall-nonce`, `--proxy` must be provided, as well") @@ -371,6 +424,15 @@ def prepare_nonce_in_args(args: Any): args.nonce = account.nonce +def get_current_nonce_for_address(address: Address, proxy_url: Union[str, None]) -> int: + if not proxy_url: + raise ArgumentsNotProvidedError("If `--nonce` is not explicitly provided, `--proxy` must be provided") + + network_provider_config = config.get_config_for_network_providers() + proxy = ProxyNetworkProvider(url=proxy_url, config=network_provider_config) + return proxy.get_account(address).nonce + + def prepare_chain_id_in_args(args: Any): if not args.chain and not args.proxy: raise ArgumentsNotProvidedError("chain ID cannot be decided: `--chain` or `--proxy` should be provided") diff --git a/multiversx_sdk_cli/cli_transactions.py b/multiversx_sdk_cli/cli_transactions.py index 22e6fb04..eeb96eaf 100644 --- a/multiversx_sdk_cli/cli_transactions.py +++ b/multiversx_sdk_cli/cli_transactions.py @@ -33,6 +33,8 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: cli_shared.add_broadcast_args(sub) cli_shared.add_proxy_arg(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) + sub.add_argument( "--wait-result", action="store_true", @@ -87,6 +89,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: cli_shared.add_proxy_arg(sub) cli_shared.add_guardian_args(sub) cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) sub.set_defaults(func=sign_transaction) sub = cli_shared.add_command_subparser( diff --git a/multiversx_sdk_cli/cli_validators.py b/multiversx_sdk_cli/cli_validators.py index 0c23e73a..51e7f09c 100644 --- a/multiversx_sdk_cli/cli_validators.py +++ b/multiversx_sdk_cli/cli_validators.py @@ -124,6 +124,7 @@ def _add_common_arguments(args: List[str], sub: Any): cli_shared.add_broadcast_args(sub) cli_shared.add_outfile_arg(sub, what="signed transaction, hash") cli_shared.add_guardian_wallet_args(args, sub) + cli_shared.add_relayed_v3_wallet_args(args, sub) def _add_nodes_arg(sub: Any): diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 42984dee..92ea1c30 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import Any, List, Optional, Protocol, Union +from typing import Any, Optional, Protocol, Union from multiversx_sdk import ( Address, @@ -59,7 +59,7 @@ def prepare_deploy_transaction( self, owner: Account, bytecode: Path, - arguments: Union[List[Any], None], + arguments: Union[list[Any], None], should_prepare_args: bool, upgradeable: bool, readable: bool, @@ -70,7 +70,8 @@ def prepare_deploy_transaction( nonce: int, version: int, options: int, - guardian: Address, + guardian: Union[Address, None], + relayer: Union[Address, None], ) -> Transaction: args = arguments if arguments else [] if should_prepare_args: @@ -91,6 +92,7 @@ def prepare_deploy_transaction( tx.version = version tx.options = options tx.guardian = guardian + tx.relayer = relayer tx.signature = bytes.fromhex(owner.sign_transaction(tx)) return tx @@ -100,15 +102,16 @@ def prepare_execute_transaction( caller: Account, contract: Address, function: str, - arguments: Union[List[Any], None], + arguments: Union[list[Any], None], should_prepare_args: bool, gas_limit: int, value: int, - transfers: Union[List[str], None], + transfers: Union[list[str], None], nonce: int, version: int, options: int, - guardian: Address, + guardian: Union[Address, None], + relayer: Union[Address, None], ) -> Transaction: token_transfers = self._prepare_token_transfers(transfers) if transfers else [] @@ -129,6 +132,7 @@ def prepare_execute_transaction( tx.version = version tx.options = options tx.guardian = guardian + tx.relayer = relayer tx.signature = bytes.fromhex(caller.sign_transaction(tx)) return tx @@ -138,7 +142,7 @@ def prepare_upgrade_transaction( owner: Account, contract: Address, bytecode: Path, - arguments: Union[List[str], None], + arguments: Union[list[str], None], should_prepare_args: bool, upgradeable: bool, readable: bool, @@ -149,7 +153,8 @@ def prepare_upgrade_transaction( nonce: int, version: int, options: int, - guardian: Address, + guardian: Union[Address, None], + relayer: Union[Address, None], ) -> Transaction: args = arguments if arguments else [] if should_prepare_args: @@ -171,6 +176,7 @@ def prepare_upgrade_transaction( tx.version = version tx.options = options tx.guardian = guardian + tx.relayer = relayer tx.signature = bytes.fromhex(owner.sign_transaction(tx)) return tx @@ -180,9 +186,9 @@ def query_contract( contract_address: Address, proxy: INetworkProvider, function: str, - arguments: Optional[List[Any]], + arguments: Optional[list[Any]], should_prepare_args: bool, - ) -> List[Any]: + ) -> list[Any]: args = arguments if arguments else [] if should_prepare_args: args = self._prepare_args_for_factory(args) @@ -196,9 +202,9 @@ def query_contract( return response - def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: + def _prepare_token_transfers(self, transfers: list[str]) -> list[TokenTransfer]: token_computer = TokenComputer() - token_transfers: List[TokenTransfer] = [] + token_transfers: list[TokenTransfer] = [] for i in range(0, len(transfers) - 1, 2): identifier = transfers[i] @@ -211,8 +217,8 @@ def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: return token_transfers - def _prepare_args_for_factory(self, arguments: List[str]) -> List[Any]: - args: 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): @@ -241,7 +247,7 @@ def _hex_to_bytes(self, arg: str): return bytes.fromhex(argument) -def prepare_execute_transaction_data(function: str, arguments: List[Any]) -> str: +def prepare_execute_transaction_data(function: str, arguments: list[Any]) -> str: tx_data = function for arg in arguments: