From ccb1102b2463c8b05d0111ec5cdb1520254674d9 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 17 Sep 2025 13:37:44 +0300 Subject: [PATCH 1/3] refactoring contracts to remove wrapper --- multiversx_sdk_cli/args_converter.py | 69 +++++++ multiversx_sdk_cli/cli_contracts.py | 173 +++++++++------- multiversx_sdk_cli/cli_delegation.py | 11 +- multiversx_sdk_cli/contracts.py | 221 --------------------- multiversx_sdk_cli/signing_wrapper.py | 88 ++++++++ multiversx_sdk_cli/tests/test_contracts.py | 26 +-- multiversx_sdk_cli/tests/test_shared.py | 25 +++ 7 files changed, 286 insertions(+), 327 deletions(-) create mode 100644 multiversx_sdk_cli/args_converter.py delete mode 100644 multiversx_sdk_cli/contracts.py create mode 100644 multiversx_sdk_cli/signing_wrapper.py diff --git a/multiversx_sdk_cli/args_converter.py b/multiversx_sdk_cli/args_converter.py new file mode 100644 index 00000000..963856d9 --- /dev/null +++ b/multiversx_sdk_cli/args_converter.py @@ -0,0 +1,69 @@ +import logging +from typing import Any + +from multiversx_sdk import Address +from multiversx_sdk.abi import ( + AddressValue, + BigUIntValue, + BoolValue, + BytesValue, + StringValue, +) + +from multiversx_sdk_cli.config_env import get_address_hrp +from multiversx_sdk_cli.constants import ( + ADDRESS_PREFIX, + FALSE_STR_LOWER, + HEX_PREFIX, + MAINCHAIN_ADDRESS_HRP, + STR_PREFIX, + TRUE_STR_LOWER, +) +from multiversx_sdk_cli.errors import BadUserInput + +logger = logging.getLogger("args_converter") + + +def convert_args_to_typed_values(arguments: list[str]) -> list[Any]: + args: list[Any] = [] + + for arg in arguments: + if arg.startswith(HEX_PREFIX): + args.append(BytesValue(_hex_to_bytes(arg))) + elif arg.isnumeric(): + args.append(BigUIntValue(int(arg))) + elif arg.startswith(ADDRESS_PREFIX): + args.append(AddressValue.new_from_address(Address.new_from_bech32(arg[len(ADDRESS_PREFIX) :]))) + elif arg.startswith(MAINCHAIN_ADDRESS_HRP): + # this flow will be removed in the future + logger.warning( + "Address argument has no prefix. This flow will be removed in the future. Please provide each address using the `addr:` prefix. (e.g. --arguments addr:erd1...)" + ) + args.append(AddressValue.new_from_address(Address.new_from_bech32(arg))) + elif arg.startswith(get_address_hrp()): + args.append(AddressValue.new_from_address(Address.new_from_bech32(arg))) + elif arg.lower() == FALSE_STR_LOWER: + args.append(BoolValue(False)) + elif arg.lower() == TRUE_STR_LOWER: + args.append(BoolValue(True)) + elif arg.startswith(STR_PREFIX): + args.append(StringValue(arg[len(STR_PREFIX) :])) + else: + raise 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() + argument = _ensure_even_length(argument) + return bytes.fromhex(argument) + + +def _ensure_even_length(string: str) -> str: + if len(string) % 2 == 1: + return "0" + string + return string diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index bcbc0a96..c819285c 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -11,12 +11,13 @@ AddressComputer, Message, ProxyNetworkProvider, + SmartContractController, Transaction, - TransactionsFactoryConfig, ) from multiversx_sdk.abi import Abi from multiversx_sdk_cli import cli_shared, utils +from multiversx_sdk_cli.args_converter import convert_args_to_typed_values from multiversx_sdk_cli.args_validation import ( validate_broadcast_args, validate_chain_id_args, @@ -28,9 +29,10 @@ from multiversx_sdk_cli.config_env import MxpyEnv 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 from multiversx_sdk_cli.docker import is_docker_installed, run_docker -from multiversx_sdk_cli.errors import DockerMissingError +from multiversx_sdk_cli.errors import DockerMissingError, QueryContractError +from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData +from multiversx_sdk_cli.signing_wrapper import SigningWrapper from multiversx_sdk_cli.ux import show_warning logger = logging.getLogger("cli.contracts") @@ -295,6 +297,7 @@ def _add_arguments_arg(sub: Any): sub.add_argument( "--arguments", nargs="+", + default=[], 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 addr:erd1[..]", ) @@ -315,6 +318,31 @@ def build(args: Any): show_warning(message) +def _initialize_controller(args: Any) -> SmartContractController: + chain_id = cli_shared.get_chain_id(args.proxy, args.chain) + config = get_config_for_network_providers() + proxy_url = args.proxy if args.proxy else "" + proxy = ProxyNetworkProvider(url=proxy_url, config=config) + abi = Abi.load(Path(args.abi)) if args.abi else None + gas_estimator = cli_shared.initialize_gas_limit_estimator(args) + + return SmartContractController( + chain_id=chain_id, + network_provider=proxy, + abi=abi, + gas_limit_estimator=gas_estimator, + ) + + +def _sign_transaction(transaction: Transaction, sender: Any, guardian_and_relayer_data: GuardianRelayerData): + signer = SigningWrapper() + signer.sign_transaction( + transaction=transaction, + sender=sender, + guardian_and_relayer=guardian_and_relayer_data, + ) + + def deploy(args: Any): logger.debug("deploy") @@ -328,31 +356,25 @@ def deploy(args: Any): args=args, ) - chain_id = cli_shared.get_chain_id(args.proxy, args.chain) - config = TransactionsFactoryConfig(chain_id) - - abi = Abi.load(Path(args.abi)) if args.abi else None - gas_estimator = cli_shared.initialize_gas_limit_estimator(args) - contract = SmartContract(config, abi, gas_estimator) - arguments, should_prepare_args = _get_contract_arguments(args) + if should_prepare_args: + arguments = convert_args_to_typed_values(arguments) - tx = contract.prepare_deploy_transaction( - owner=sender, + controller = _initialize_controller(args) + tx = controller.create_transaction_for_deploy( + sender=sender, + nonce=sender.nonce, bytecode=Path(args.bytecode), arguments=arguments, - should_prepare_args=should_prepare_args, - upgradeable=args.metadata_upgradeable, - readable=args.metadata_readable, - payable=args.metadata_payable, - payable_by_sc=args.metadata_payable_by_sc, + 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, + guardian=guardian_and_relayer_data.guardian.address if guardian_and_relayer_data.guardian else None, + relayer=guardian_and_relayer_data.relayer.address if guardian_and_relayer_data.relayer else None, gas_limit=args.gas_limit, - gas_price=int(args.gas_price), - value=int(args.value), - nonce=sender.nonce, - version=int(args.version), - options=int(args.options), - guardian_and_relayer_data=guardian_and_relayer_data, + gas_price=args.gas_price, ) address_computer = AddressComputer(NUMBER_OF_SHARDS) @@ -361,8 +383,13 @@ def deploy(args: Any): logger.info("Contract address: %s", contract_address.to_bech32()) cli_config = MxpyEnv.from_active_env() - utils.log_explorer_contract_address(args.chain, contract_address.to_bech32(), cli_config.explorer_url) + utils.log_explorer_contract_address( + chain=controller.factory.config.chain_id, + address=contract_address.to_bech32(), + explorer_url=cli_config.explorer_url, + ) + _sign_transaction(tx, sender, guardian_and_relayer_data) _send_or_simulate(tx, contract_address, args) @@ -379,36 +406,32 @@ def call(args: Any): args=args, ) - chain_id = cli_shared.get_chain_id(args.proxy, args.chain) - config = TransactionsFactoryConfig(chain_id) - - abi = Abi.load(Path(args.abi)) if args.abi else None - gas_estimator = cli_shared.initialize_gas_limit_estimator(args) - contract = SmartContract(config, abi, gas_estimator) - arguments, should_prepare_args = _get_contract_arguments(args) - contract_address = Address.new_from_bech32(args.contract) + if should_prepare_args: + arguments = convert_args_to_typed_values(arguments) - token_transfers = None + token_transfers = [] if args.token_transfers: token_transfers = cli_shared.prepare_token_transfers(args.token_transfers) - tx = contract.prepare_execute_transaction( - caller=sender, + contract_address = Address.new_from_bech32(args.contract) + controller = _initialize_controller(args) + + tx = controller.create_transaction_for_execute( + sender=sender, + nonce=sender.nonce, contract=contract_address, function=args.function, arguments=arguments, - should_prepare_args=should_prepare_args, - gas_limit=args.gas_limit, - gas_price=int(args.gas_price), - value=int(args.value), + native_transfer_amount=int(args.value), token_transfers=token_transfers, - nonce=sender.nonce, - version=int(args.version), - options=int(args.options), - guardian_and_relayer_data=guardian_and_relayer_data, + guardian=guardian_and_relayer_data.guardian.address if guardian_and_relayer_data.guardian else None, + relayer=guardian_and_relayer_data.relayer.address if guardian_and_relayer_data.relayer else None, + gas_limit=args.gas_limit, + gas_price=args.gas_price, ) + _sign_transaction(tx, sender, guardian_and_relayer_data) _send_or_simulate(tx, contract_address, args) @@ -425,35 +448,31 @@ def upgrade(args: Any): args=args, ) - chain_id = cli_shared.get_chain_id(args.proxy, args.chain) - config = TransactionsFactoryConfig(chain_id) - - abi = Abi.load(Path(args.abi)) if args.abi else None - gas_estimator = cli_shared.initialize_gas_limit_estimator(args) - contract = SmartContract(config, abi, gas_estimator) - arguments, should_prepare_args = _get_contract_arguments(args) + if should_prepare_args: + arguments = convert_args_to_typed_values(arguments) + contract_address = Address.new_from_bech32(args.contract) + controller = _initialize_controller(args) - tx = contract.prepare_upgrade_transaction( - owner=sender, + tx = controller.create_transaction_for_upgrade( + sender=sender, + nonce=sender.nonce, contract=contract_address, bytecode=Path(args.bytecode), arguments=arguments, - should_prepare_args=should_prepare_args, - upgradeable=args.metadata_upgradeable, - readable=args.metadata_readable, - payable=args.metadata_payable, - payable_by_sc=args.metadata_payable_by_sc, + 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, + guardian=guardian_and_relayer_data.guardian.address if guardian_and_relayer_data.guardian else None, + relayer=guardian_and_relayer_data.relayer.address if guardian_and_relayer_data.relayer else None, gas_limit=args.gas_limit, - gas_price=int(args.gas_price), - value=int(args.value), - nonce=sender.nonce, - version=int(args.version), - options=int(args.options), - guardian_and_relayer_data=guardian_and_relayer_data, + gas_price=args.gas_price, ) + _sign_transaction(tx, sender, guardian_and_relayer_data) _send_or_simulate(tx, contract_address, args) @@ -462,26 +481,32 @@ def query(args: Any): validate_proxy_argument(args) - # 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) + contract_address = Address.new_from_bech32(args.contract) + function = args.function arguments, should_prepare_args = _get_contract_arguments(args) - contract_address = Address.new_from_bech32(args.contract) + if should_prepare_args: + arguments = convert_args_to_typed_values(arguments) network_provider_config = get_config_for_network_providers() proxy = ProxyNetworkProvider(url=args.proxy, config=network_provider_config) - function = args.function - result = contract.query_contract( - contract_address=contract_address, - proxy=proxy, - function=function, - arguments=arguments, - should_prepare_args=should_prepare_args, + controller = SmartContractController( + chain_id="", + network_provider=proxy, + abi=abi, ) + try: + result = controller.query( + contract=contract_address, + function=function, + arguments=arguments, + ) + except Exception as e: + raise QueryContractError("Couldn't query contract: ", e) + utils.dump_out_json(result) diff --git a/multiversx_sdk_cli/cli_delegation.py b/multiversx_sdk_cli/cli_delegation.py index 941f3407..09123091 100644 --- a/multiversx_sdk_cli/cli_delegation.py +++ b/multiversx_sdk_cli/cli_delegation.py @@ -20,10 +20,10 @@ validate_proxy_argument, validate_receiver_args, ) -from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController from multiversx_sdk_cli.config import get_config_for_network_providers from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData from multiversx_sdk_cli.interfaces import IAccount +from multiversx_sdk_cli.signing_wrapper import SigningWrapper def setup_parser(args: list[str], subparsers: Any) -> Any: @@ -411,14 +411,11 @@ def _get_delegation_controller(args: Any): def _sign_transaction(transaction: Transaction, sender: IAccount, guardian_and_relayer_data: GuardianRelayerData): - base = BaseTransactionsController() - base.sign_transaction( + signer = SigningWrapper() + signer.sign_transaction( transaction=transaction, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, - guardian_service_url=guardian_and_relayer_data.guardian_service_url, - guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, + guardian_and_relayer=guardian_and_relayer_data, ) diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py deleted file mode 100644 index 91f7a7c2..00000000 --- a/multiversx_sdk_cli/contracts.py +++ /dev/null @@ -1,221 +0,0 @@ -import logging -from pathlib import Path -from typing import Any, Optional, Protocol, Union - -from multiversx_sdk import ( - Address, - AwaitingOptions, - GasLimitEstimator, - SmartContractController, - SmartContractQuery, - SmartContractQueryResponse, - SmartContractTransactionsFactory, - TokenTransfer, - Transaction, - TransactionOnNetwork, - TransactionsFactoryConfig, -) -from multiversx_sdk.abi import Abi - -from multiversx_sdk_cli import errors -from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController -from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData -from multiversx_sdk_cli.interfaces import IAccount - -logger = logging.getLogger("contracts") - - -# fmt: off -class INetworkProvider(Protocol): - def query_contract(self, query: SmartContractQuery) -> SmartContractQueryResponse: - ... - - def await_transaction_completed( - self, transaction_hash: Union[bytes, str], options: Optional[AwaitingOptions] = None - ) -> TransactionOnNetwork: - ... -# fmt: on - - -class SmartContract(BaseTransactionsController): - def __init__( - self, - config: TransactionsFactoryConfig, - abi: Optional[Abi] = None, - gas_limit_estimator: Optional[GasLimitEstimator] = None, - ): - self._abi = abi - self._config = config - self._factory = SmartContractTransactionsFactory(config, abi, gas_limit_estimator) - - def prepare_deploy_transaction( - self, - owner: IAccount, - bytecode: Path, - arguments: Union[list[Any], None], - should_prepare_args: bool, - upgradeable: bool, - readable: bool, - payable: bool, - payable_by_sc: bool, - gas_limit: Union[int, None], - gas_price: int, - value: int, - nonce: int, - version: int, - options: int, - guardian_and_relayer_data: GuardianRelayerData, - ) -> Transaction: - args = arguments if arguments else [] - if should_prepare_args: - args = self._convert_args_to_typed_values(args) - - tx = self._factory.create_transaction_for_deploy( - sender=owner.address, - bytecode=bytecode, - gas_limit=gas_limit, - arguments=args, - native_transfer_amount=value, - is_upgradeable=upgradeable, - is_readable=readable, - is_payable=payable, - is_payable_by_sc=payable_by_sc, - ) - tx.nonce = nonce - tx.version = version - tx.options = options - tx.gas_price = gas_price - tx.guardian = guardian_and_relayer_data.guardian_address - tx.relayer = guardian_and_relayer_data.relayer_address - - self.sign_transaction( - transaction=tx, - sender=owner, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, - guardian_service_url=guardian_and_relayer_data.guardian_service_url, - guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, - ) - - return tx - - def prepare_execute_transaction( - self, - caller: IAccount, - contract: Address, - function: str, - arguments: Union[list[Any], None], - should_prepare_args: bool, - gas_limit: Union[int, None], - gas_price: int, - value: int, - token_transfers: Union[list[TokenTransfer], None], - nonce: int, - version: int, - options: int, - guardian_and_relayer_data: GuardianRelayerData, - ) -> Transaction: - args = arguments if arguments else [] - if should_prepare_args: - args = self._convert_args_to_typed_values(args) - - tx = self._factory.create_transaction_for_execute( - sender=caller.address, - contract=contract, - function=function, - gas_limit=gas_limit, - arguments=args, - native_transfer_amount=value, - token_transfers=token_transfers or [], - ) - tx.nonce = nonce - tx.version = version - tx.options = options - tx.gas_price = gas_price - tx.guardian = guardian_and_relayer_data.guardian_address - tx.relayer = guardian_and_relayer_data.relayer_address - - self.sign_transaction( - transaction=tx, - sender=caller, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, - guardian_service_url=guardian_and_relayer_data.guardian_service_url, - guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, - ) - - return tx - - def prepare_upgrade_transaction( - self, - owner: IAccount, - contract: Address, - bytecode: Path, - arguments: Union[list[str], None], - should_prepare_args: bool, - upgradeable: bool, - readable: bool, - payable: bool, - payable_by_sc: bool, - gas_limit: Union[int, None], - gas_price: int, - value: int, - nonce: int, - version: int, - options: int, - guardian_and_relayer_data: GuardianRelayerData, - ) -> Transaction: - args = arguments if arguments else [] - if should_prepare_args: - args = self._convert_args_to_typed_values(args) - - tx = self._factory.create_transaction_for_upgrade( - sender=owner.address, - contract=contract, - bytecode=bytecode, - gas_limit=gas_limit, - arguments=args, - native_transfer_amount=value, - is_upgradeable=upgradeable, - is_readable=readable, - is_payable=payable, - is_payable_by_sc=payable_by_sc, - ) - tx.nonce = nonce - tx.version = version - tx.options = options - tx.gas_price = gas_price - tx.guardian = guardian_and_relayer_data.guardian_address - tx.relayer = guardian_and_relayer_data.relayer_address - - self.sign_transaction( - transaction=tx, - sender=owner, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, - guardian_service_url=guardian_and_relayer_data.guardian_service_url, - guardian_2fa_code=guardian_and_relayer_data.guardian_2fa_code, - ) - - return tx - - def query_contract( - self, - contract_address: Address, - 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._convert_args_to_typed_values(args) - - sc_query_controller = SmartContractController(self._config.chain_id, proxy, self._abi) - - try: - response = sc_query_controller.query(contract=contract_address, function=function, arguments=args) - except Exception as e: - raise errors.QueryContractError("Couldn't query contract: ", e) - - return response diff --git a/multiversx_sdk_cli/signing_wrapper.py b/multiversx_sdk_cli/signing_wrapper.py new file mode 100644 index 00000000..4fa4198a --- /dev/null +++ b/multiversx_sdk_cli/signing_wrapper.py @@ -0,0 +1,88 @@ +from typing import Optional, Union + +from multiversx_sdk import LedgerAccount, Transaction, TransactionComputer + +from multiversx_sdk_cli.cosign_transaction import cosign_transaction +from multiversx_sdk_cli.errors import TransactionSigningError +from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData +from multiversx_sdk_cli.interfaces import IAccount + + +class SigningWrapper: + def __init__(self): + pass + + def sign_transaction( + self, + transaction: Transaction, + sender: Optional[IAccount] = None, + guardian_and_relayer: GuardianRelayerData = GuardianRelayerData(), + ): + """Signs the transaction using the sender's account and, if required, additionally signs with the guardian's and relayer's accounts. Ensures the appropriate transaction options are set as needed.""" + self._set_options_for_guarded_transaction_if_needed(transaction) + + guardian = guardian_and_relayer.guardian + relayer = guardian_and_relayer.relayer + guardian_service_url = guardian_and_relayer.guardian_service_url + guardian_2fa_code = guardian_and_relayer.guardian_2fa_code + + self._set_options_for_hash_signing_if_needed(transaction, sender, guardian, relayer) + + if sender: + try: + transaction.signature = sender.sign_transaction(transaction) + except Exception as e: + raise TransactionSigningError(f"Could not sign transaction: {str(e)}") + + self._sign_guarded_transaction_if_guardian( + transaction, + guardian, + guardian_service_url, + guardian_2fa_code, + ) + self._sign_relayed_transaction_if_relayer(transaction, relayer) + + def _set_options_for_guarded_transaction_if_needed(self, transaction: Transaction): + if transaction.guardian: + transaction_computer = TransactionComputer() + transaction_computer.apply_guardian(transaction, transaction.guardian) + + def _set_options_for_hash_signing_if_needed( + self, + transaction: Transaction, + sender: Union[IAccount, None], + guardian: Union[IAccount, None], + relayer: Union[IAccount, None], + ): + if ( + isinstance(sender, LedgerAccount) + or isinstance(guardian, LedgerAccount) + or isinstance(relayer, LedgerAccount) + ): + transaction_computer = TransactionComputer() + transaction_computer.apply_options_for_hash_signing(transaction) + + def _sign_guarded_transaction_if_guardian( + self, + transaction: Transaction, + guardian: Union[IAccount, None], + guardian_service_url: Union[str, None], + guardian_2fa_code: Union[str, None], + ) -> Transaction: + # If the guardian account is provided, we sign locally. Otherwise, we reach for the trusted cosign service. + if guardian: + try: + transaction.guardian_signature = guardian.sign_transaction(transaction) + except Exception as e: + raise TransactionSigningError(f"Could not sign transaction: {str(e)}") + elif transaction.guardian and guardian_service_url and guardian_2fa_code: + cosign_transaction(transaction, guardian_service_url, guardian_2fa_code) + + return transaction + + def _sign_relayed_transaction_if_relayer(self, transaction: Transaction, relayer: Union[IAccount, None]): + if relayer and transaction.relayer: + try: + transaction.relayer_signature = relayer.sign_transaction(transaction) + except Exception as e: + raise TransactionSigningError(f"Could not sign transaction: {str(e)}") diff --git a/multiversx_sdk_cli/tests/test_contracts.py b/multiversx_sdk_cli/tests/test_contracts.py index d3397f99..21f8b59d 100644 --- a/multiversx_sdk_cli/tests/test_contracts.py +++ b/multiversx_sdk_cli/tests/test_contracts.py @@ -2,10 +2,9 @@ from pathlib import Path from Cryptodome.Hash import keccak -from multiversx_sdk import Account, Address, TransactionsFactoryConfig +from multiversx_sdk import Account, Address from multiversx_sdk_cli.contract_verification import _create_request_signature -from multiversx_sdk_cli.contracts import SmartContract logging.basicConfig(level=logging.INFO) @@ -27,26 +26,3 @@ def test_contract_verification_create_request_signature(): signature.hex() == "30111258cc42ea08e0c6a3e053cc7086a88d614b8b119a244904e9a19896c73295b2fe5c520a1cb07cfe20f687deef9f294a0a05071e85c78a70a448ea5f0605" ) - - -def test_prepare_args_for_factories(): - sc = SmartContract(TransactionsFactoryConfig("mock")) - args = [ - "0x5", - "123", - "false", - "true", - "str:test-string", - "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", - ] - - arguments = sc._convert_args_to_typed_values(args) - assert arguments[0].get_payload() == b"\x05" - assert arguments[1].get_payload() == 123 - assert arguments[2].get_payload() is False - assert arguments[3].get_payload() is True - assert arguments[4].get_payload() == "test-string" - assert ( - arguments[5].get_payload() - == Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th").get_public_key() - ) diff --git a/multiversx_sdk_cli/tests/test_shared.py b/multiversx_sdk_cli/tests/test_shared.py index 53c8b21d..8c21bac5 100644 --- a/multiversx_sdk_cli/tests/test_shared.py +++ b/multiversx_sdk_cli/tests/test_shared.py @@ -1,3 +1,6 @@ +from multiversx_sdk import Address + +from multiversx_sdk_cli.args_converter import convert_args_to_typed_values from multiversx_sdk_cli.cli_shared import prepare_token_transfers @@ -31,3 +34,25 @@ def test_prepare_token_tranfers(): assert transfers[3].token.identifier == "META-777777" assert transfers[3].token.nonce == 16 assert transfers[3].amount == 123456789 + + +def test_prepare_args_for_factories(): + args = [ + "0x5", + "123", + "false", + "true", + "str:test-string", + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ] + + arguments = convert_args_to_typed_values(args) + assert arguments[0].get_payload() == b"\x05" + assert arguments[1].get_payload() == 123 + assert arguments[2].get_payload() is False + assert arguments[3].get_payload() is True + assert arguments[4].get_payload() == "test-string" + assert ( + arguments[5].get_payload() + == Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th").get_public_key() + ) From 58e966316a4534514ac88e24aa15d3faafc76d2d Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 22 Sep 2025 16:25:57 +0300 Subject: [PATCH 2/3] continued refactoring for delegation commands --- multiversx_sdk_cli/cli_delegation.py | 219 +++++++++------------------ multiversx_sdk_cli/cli_shared.py | 76 ++++++++-- 2 files changed, 136 insertions(+), 159 deletions(-) diff --git a/multiversx_sdk_cli/cli_delegation.py b/multiversx_sdk_cli/cli_delegation.py index 3c0c7728..845e1bab 100644 --- a/multiversx_sdk_cli/cli_delegation.py +++ b/multiversx_sdk_cli/cli_delegation.py @@ -1,12 +1,11 @@ from pathlib import Path -from typing import Any, Optional +from typing import Any from multiversx_sdk import ( Address, DelegationController, DelegationTransactionsOutcomeParser, ProxyNetworkProvider, - Transaction, ValidatorPublicKey, ValidatorsController, ValidatorsSigners, @@ -21,10 +20,6 @@ validate_receiver_args, ) from multiversx_sdk_cli.config import get_config_for_network_providers -from multiversx_sdk_cli.constants import DEFAULT_TX_VERSION -from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData -from multiversx_sdk_cli.interfaces import IAccount -from multiversx_sdk_cli.signing_wrapper import SigningWrapper def setup_parser(args: list[str], subparsers: Any) -> Any: @@ -411,19 +406,6 @@ def _get_delegation_controller(args: Any): ) -def _sign_transaction( - transaction: Transaction, - sender: Optional[IAccount] = None, - guardian_and_relayer_data: GuardianRelayerData = GuardianRelayerData(), -): - signer = SigningWrapper() - signer.sign_transaction( - transaction=transaction, - sender=sender, - guardian_and_relayer=guardian_and_relayer_data, - ) - - def do_create_delegation_contract(args: Any): validate_arguments(args) @@ -446,18 +428,13 @@ def do_create_delegation_contract(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - altered = _alter_version_and_options_if_provided(args, tx) - if altered: # sign only if something was altered - _sign_transaction(tx, sender, guardian_and_relayer_data) - else: - _sign_transaction(tx, None, guardian_and_relayer_data) # sign only with guardian/relayer if needed cli_shared.send_or_simulate(tx, args) @@ -502,15 +479,13 @@ def add_new_nodes(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -556,15 +531,13 @@ def remove_nodes(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -610,15 +583,14 @@ def stake_nodes(args: Any): gas_limit=args.gas_limit, gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -654,15 +626,13 @@ def unbond_nodes(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -690,15 +660,13 @@ def unstake_nodes(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -729,15 +697,13 @@ def unjail_nodes(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -764,15 +730,13 @@ def delegate(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -796,15 +760,13 @@ def claim_rewards(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -828,15 +790,13 @@ def redelegate_rewards(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -863,15 +823,13 @@ def undelegate(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -895,15 +853,13 @@ def withdraw(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -928,15 +884,13 @@ def change_service_fee(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -961,15 +915,13 @@ def modify_delegation_cap(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -1010,15 +962,13 @@ def automatic_activation(args: Any): else: raise errors.BadUsage("Both set and unset automatic activation are False") - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -1058,15 +1008,13 @@ def redelegate_cap(args: Any): else: raise errors.BadUsage("Either set or unset should be True") - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -1093,15 +1041,13 @@ def set_metadata(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) @@ -1129,28 +1075,11 @@ def make_new_contract_from_validator_data(args: Any): gas_price=args.gas_price, ) - cli_shared.set_options_for_hash_signing_if_needed( - transaction=tx, + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - guardian=guardian_and_relayer_data.guardian, - relayer=guardian_and_relayer_data.relayer, + guardian_and_relayer_data=guardian_and_relayer_data, ) - _alter_version_and_options_if_provided(args, tx) - _sign_transaction(tx, sender, guardian_and_relayer_data) cli_shared.send_or_simulate(tx, args) - - -def _alter_version_and_options_if_provided(args: Any, transaction: Transaction) -> bool: - """Alters the transaction version and options if they are provided in args. Returns True if any alteration was made, False otherwise.""" - altered = False - - if args.options: - transaction.options = args.options - altered = True - - if args.version != DEFAULT_TX_VERSION: - transaction.version = args.version - altered = True - - return altered diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index 8aaeb226..0dd67fdc 100644 --- a/multiversx_sdk_cli/cli_shared.py +++ b/multiversx_sdk_cli/cli_shared.py @@ -54,6 +54,7 @@ ) from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData from multiversx_sdk_cli.interfaces import IAccount +from multiversx_sdk_cli.signing_wrapper import SigningWrapper from multiversx_sdk_cli.simulation import Simulator from multiversx_sdk_cli.transactions import send_and_wait_for_result from multiversx_sdk_cli.utils import log_explorer_transaction @@ -352,7 +353,7 @@ def parse_omit_fields_arg(args: Any) -> list[str]: return cast(list[str], parsed) -def _options_set_for_hash_signing(args: Any) -> bool: +def _has_options_set_for_hash_signing(args: Any) -> bool: if hasattr(args, "options") and args.options: if args.options & TRANSACTION_OPTIONS_TX_HASH_SIGN == TRANSACTION_OPTIONS_TX_HASH_SIGN: return True @@ -365,7 +366,7 @@ def prepare_account(args: Any): if args.pem: acc = Account.new_from_pem(file_path=Path(args.pem), index=args.sender_wallet_index, hrp=hrp) - if _options_set_for_hash_signing(args): + if _has_options_set_for_hash_signing(args): acc.use_hash_signing = True return acc elif args.keyfile: @@ -374,7 +375,7 @@ def prepare_account(args: Any): try: acc = Account.new_from_keystore(Path(args.keyfile), password=password, address_index=index, hrp=hrp) - if _options_set_for_hash_signing(args): + if _has_options_set_for_hash_signing(args): acc.use_hash_signing = True return acc except Exception as e: @@ -386,12 +387,12 @@ def prepare_account(args: Any): raise LedgerError(str(e)) elif args.sender: acc = load_wallet_by_alias(alias=args.sender, hrp=hrp) - if _options_set_for_hash_signing(args): + if _has_options_set_for_hash_signing(args): acc.use_hash_signing = True return acc else: acc = load_default_wallet(hrp=hrp) - if _options_set_for_hash_signing(args): + if _has_options_set_for_hash_signing(args): acc.use_hash_signing = True return acc @@ -842,23 +843,70 @@ def initialize_gas_limit_estimator(args: Any) -> Union[GasLimitEstimator, None]: def set_options_for_hash_signing_if_needed( transaction: Transaction, - sender: Union[IAccount, None], guardian: Union[IAccount, None], relayer: Union[IAccount, None], ): transaction_computer = TransactionComputer() - if isinstance(sender, LedgerAccount) or isinstance(guardian, LedgerAccount) or isinstance(relayer, LedgerAccount): - transaction_computer.apply_options_for_hash_signing(transaction) - return - - if sender and sender.use_hash_signing: - transaction_computer.apply_options_for_hash_signing(transaction) - return - if guardian and guardian.use_hash_signing: transaction_computer.apply_options_for_hash_signing(transaction) return if relayer and relayer.use_hash_signing: transaction_computer.apply_options_for_hash_signing(transaction) + + +def alter_transaction_and_sign_again_if_needed( + args: Any, + tx: Transaction, + sender: IAccount, + guardian_and_relayer_data: GuardianRelayerData, +): + set_options_for_hash_signing_if_needed( + transaction=tx, + guardian=guardian_and_relayer_data.guardian, + relayer=guardian_and_relayer_data.relayer, + ) + + altered = _alter_version_and_options_if_provided( + args=args, + final_transaction=tx, + ) + + if altered: # sign only if something was altered + _sign_transaction(tx, sender, guardian_and_relayer_data) + else: + _sign_transaction(tx, None, guardian_and_relayer_data) # sign only with guardian/relayer if needed + + +def _alter_version_and_options_if_provided( + args: Any, + final_transaction: Transaction, +) -> bool: + """Alters the transaction version and options if they are provided in args. + Returns True if any alteration was made, False otherwise. + """ + altered = False + + if args.version != DEFAULT_TX_VERSION and final_transaction.version != args.version: + final_transaction.version = args.version + altered = True + + if args.options and final_transaction.options != args.options: + final_transaction.options = args.options + altered = True + + return altered + + +def _sign_transaction( + transaction: Transaction, + sender: Optional[IAccount] = None, + guardian_and_relayer_data: GuardianRelayerData = GuardianRelayerData(), +): + signer = SigningWrapper() + signer.sign_transaction( + transaction=transaction, + sender=sender, + guardian_and_relayer=guardian_and_relayer_data, + ) From da6288a0494cd49348ab790afcd610a641031430 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 22 Sep 2025 17:01:47 +0300 Subject: [PATCH 3/3] used shared method --- multiversx_sdk_cli/cli_contracts.py | 43 +++++++++++++++++++---------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index c819285c..7216222b 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -30,9 +30,7 @@ from multiversx_sdk_cli.constants import NUMBER_OF_SHARDS from multiversx_sdk_cli.contract_verification import trigger_contract_verification from multiversx_sdk_cli.docker import is_docker_installed, run_docker -from multiversx_sdk_cli.errors import DockerMissingError, QueryContractError -from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData -from multiversx_sdk_cli.signing_wrapper import SigningWrapper +from multiversx_sdk_cli.errors import BadUsage, DockerMissingError, QueryContractError from multiversx_sdk_cli.ux import show_warning logger = logging.getLogger("cli.contracts") @@ -334,13 +332,9 @@ def _initialize_controller(args: Any) -> SmartContractController: ) -def _sign_transaction(transaction: Transaction, sender: Any, guardian_and_relayer_data: GuardianRelayerData): - signer = SigningWrapper() - signer.sign_transaction( - transaction=transaction, - sender=sender, - guardian_and_relayer=guardian_and_relayer_data, - ) +def _ensure_args_for_gas_estimation(args: Any): + if not args.proxy and not args.gas_limit: + raise BadUsage("To estimate the gas limit, you need to provide `--proxy` or set a value using `--gas-limit`") def deploy(args: Any): @@ -350,6 +344,8 @@ def deploy(args: Any): validate_broadcast_args(args) validate_chain_id_args(args) + _ensure_args_for_gas_estimation(args) + sender = cli_shared.prepare_sender(args) guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( sender=sender.address.to_bech32(), @@ -384,12 +380,17 @@ def deploy(args: Any): cli_config = MxpyEnv.from_active_env() utils.log_explorer_contract_address( - chain=controller.factory.config.chain_id, + chain=cli_shared.get_chain_id(args.proxy, args.chain), address=contract_address.to_bech32(), explorer_url=cli_config.explorer_url, ) - _sign_transaction(tx, sender, guardian_and_relayer_data) + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, + sender=sender, + guardian_and_relayer_data=guardian_and_relayer_data, + ) _send_or_simulate(tx, contract_address, args) @@ -400,6 +401,8 @@ def call(args: Any): validate_broadcast_args(args) validate_chain_id_args(args) + _ensure_args_for_gas_estimation(args) + sender = cli_shared.prepare_sender(args) guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( sender=sender.address.to_bech32(), @@ -431,7 +434,12 @@ def call(args: Any): gas_price=args.gas_price, ) - _sign_transaction(tx, sender, guardian_and_relayer_data) + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, + sender=sender, + guardian_and_relayer_data=guardian_and_relayer_data, + ) _send_or_simulate(tx, contract_address, args) @@ -442,6 +450,8 @@ def upgrade(args: Any): validate_broadcast_args(args) validate_chain_id_args(args) + _ensure_args_for_gas_estimation(args) + sender = cli_shared.prepare_sender(args) guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( sender=sender.address.to_bech32(), @@ -472,7 +482,12 @@ def upgrade(args: Any): gas_price=args.gas_price, ) - _sign_transaction(tx, sender, guardian_and_relayer_data) + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, + sender=sender, + guardian_and_relayer_data=guardian_and_relayer_data, + ) _send_or_simulate(tx, contract_address, args)