diff --git a/multiversx_sdk_cli/cli_transactions.py b/multiversx_sdk_cli/cli_transactions.py index 1249cafd..fd97ae51 100644 --- a/multiversx_sdk_cli/cli_transactions.py +++ b/multiversx_sdk_cli/cli_transactions.py @@ -2,7 +2,12 @@ from pathlib import Path from typing import Any -from multiversx_sdk import Address, ProxyNetworkProvider, TransactionComputer +from multiversx_sdk import ( + Address, + ProxyNetworkProvider, + TransactionComputer, + TransfersController, +) from multiversx_sdk_cli import cli_shared, utils from multiversx_sdk_cli.args_validation import ( @@ -13,14 +18,12 @@ validate_proxy_argument, validate_receiver_args, ) -from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController from multiversx_sdk_cli.cli_output import CLIOutputBuilder from multiversx_sdk_cli.config import get_config_for_network_providers from multiversx_sdk_cli.errors import BadUsage, IncorrectWalletError, NoWalletProvided -from multiversx_sdk_cli.transactions import ( - TransactionsController, - load_transaction_from_file, -) +from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData +from multiversx_sdk_cli.signing_wrapper import SigningWrapper +from multiversx_sdk_cli.transactions import load_transaction_from_file logger = logging.getLogger("cli.transactions") @@ -101,38 +104,59 @@ def create_transaction(args: Any): validate_broadcast_args(args) validate_chain_id_args(args) + if args.data_file: + args.data = Path(args.data_file).read_text() + + transfers = getattr(args, "token_transfers", None) + + if transfers and args.data: + raise BadUsage("You cannot provide both data and token transfers") + sender = cli_shared.prepare_sender(args) guardian_and_relayer_data = cli_shared.get_guardian_and_relayer_data( sender=sender.address.to_bech32(), args=args, ) - if args.data_file: - args.data = Path(args.data_file).read_text() - native_amount = int(args.value) - - transfers = getattr(args, "token_transfers", None) + receiver = Address.new_from_bech32(args.receiver) transfers = cli_shared.prepare_token_transfers(transfers) if transfers else None chain_id = cli_shared.get_chain_id(args.proxy, args.chain) gas_estimator = cli_shared.initialize_gas_limit_estimator(args) - tx_controller = TransactionsController(chain_id, gas_estimator) - - tx = tx_controller.create_transaction( + controller = TransfersController(chain_id=chain_id, gas_limit_estimator=gas_estimator) + + if not transfers: + tx = controller.create_transaction_for_native_token_transfer( + sender=sender, + nonce=sender.nonce, + receiver=receiver, + native_transfer_amount=native_amount, + data=args.data.encode() if args.data else None, + guardian=guardian_and_relayer_data.guardian_address, + relayer=guardian_and_relayer_data.relayer_address, + gas_limit=args.gas_limit, + gas_price=args.gas_price, + ) + else: + tx = controller.create_transaction_for_transfer( + sender=sender, + nonce=sender.nonce, + receiver=receiver, + native_transfer_amount=native_amount, + token_transfers=transfers, + guardian=guardian_and_relayer_data.guardian_address, + relayer=guardian_and_relayer_data.relayer_address, + gas_limit=args.gas_limit, + gas_price=args.gas_price, + ) + + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, sender=sender, - receiver=Address.new_from_bech32(args.receiver), - native_amount=native_amount, - gas_limit=args.gas_limit, - gas_price=args.gas_price, - nonce=sender.nonce, - version=args.version, - options=args.options, - token_transfers=transfers, - data=args.data, guardian_and_relayer_data=guardian_and_relayer_data, ) - cli_shared.send_or_simulate(tx, args) @@ -180,18 +204,24 @@ def sign_transaction(args: Any): tx_computer = TransactionComputer() if tx.guardian and not tx_computer.has_options_set_for_guarded_transaction(tx): - raise BadUsage("Guardian wallet provided but the transaction has incorrect options.") + raise BadUsage("Guardian wallet provided but the transaction has incorrect options") - tx_controller = BaseTransactionsController() - tx_controller.sign_transaction( - transaction=tx, - sender=sender, + guardian_and_relayer = GuardianRelayerData( guardian=guardian, + guardian_address=tx.guardian, relayer=relayer, + relayer_address=tx.relayer, guardian_service_url=args.guardian_service_url, guardian_2fa_code=args.guardian_2fa_code, ) + signer = SigningWrapper() + signer.sign_transaction( + transaction=tx, + sender=sender, + guardian_and_relayer=guardian_and_relayer, + ) + cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/dns.py b/multiversx_sdk_cli/dns.py index c1a70277..486ce0d6 100644 --- a/multiversx_sdk_cli/dns.py +++ b/multiversx_sdk_cli/dns.py @@ -6,6 +6,7 @@ AddressComputer, SmartContractQuery, SmartContractQueryResponse, + TransfersController, ) from multiversx_sdk_cli import cli_shared @@ -16,7 +17,6 @@ ) from multiversx_sdk_cli.config_env import get_address_hrp from multiversx_sdk_cli.constants import ADDRESS_ZERO_HEX -from multiversx_sdk_cli.transactions import TransactionsController MaxNumShards = 256 ShardIdentiferLen = 2 @@ -77,21 +77,26 @@ def register(args: Any): chain_id = cli_shared.get_chain_id(args.proxy, args.chain) gas_estimator = cli_shared.initialize_gas_limit_estimator(args) - controller = TransactionsController(chain_id=chain_id, gas_limit_estimator=gas_estimator) + controller = TransfersController(chain_id=chain_id, gas_limit_estimator=gas_estimator) - tx = controller.create_transaction( + tx = controller.create_transaction_for_native_token_transfer( sender=sender, receiver=receiver, - native_amount=native_amount, + native_transfer_amount=native_amount, gas_limit=args.gas_limit, gas_price=args.gas_price, nonce=sender.nonce, - version=args.version, - options=args.options, - data=data, - guardian_and_relayer_data=guardian_and_relayer_data, + data=data.encode(), + guardian=guardian_and_relayer_data.guardian_address, + relayer=guardian_and_relayer_data.relayer_address, ) + cli_shared.alter_transaction_and_sign_again_if_needed( + args=args, + tx=tx, + sender=sender, + guardian_and_relayer_data=guardian_and_relayer_data, + ) cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/tests/test_cli_transactions.py b/multiversx_sdk_cli/tests/test_cli_transactions.py index 128cf63a..fb522b58 100644 --- a/multiversx_sdk_cli/tests/test_cli_transactions.py +++ b/multiversx_sdk_cli/tests/test_cli_transactions.py @@ -463,6 +463,28 @@ def test_estimate_gas_with_multiplier(capsys: Any): assert tx_json["gasLimit"] == 75000 +def test_raise_error_when_data_and_transfers_provided(capsys: Any): + return_code = main( + [ + "tx", + "new", + "--pem", + str(testdata_path / "alice.pem"), + "--receiver", + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "--nonce", + "7", + "--chain", + "D", + "--data", + "hello", + "--token-transfers", + "TEST-123456", + ] + ) + assert return_code == 1 + + def _read_stdout(capsys: Any) -> str: stdout: str = capsys.readouterr().out.strip() return stdout diff --git a/multiversx_sdk_cli/tests/test_transactions.py b/multiversx_sdk_cli/tests/test_transactions.py deleted file mode 100644 index 8a8c4e4c..00000000 --- a/multiversx_sdk_cli/tests/test_transactions.py +++ /dev/null @@ -1,256 +0,0 @@ -from pathlib import Path - -from multiversx_sdk import Account, Address - -from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData -from multiversx_sdk_cli.transactions import TransactionsController - -testdata = Path(__file__).parent / "testdata" - - -class TestTransactionsController: - controller = TransactionsController("D") - alice = Account.new_from_pem(testdata / "alice.pem") - - def test_create_transaction_without_data_and_value(self): - guardian_relayer_data = GuardianRelayerData() - transaction = self.controller.create_transaction( - sender=self.alice, - receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), - native_amount=0, - gas_limit=50000, - gas_price=1000000000, - nonce=7, - version=2, - options=0, - guardian_and_relayer_data=guardian_relayer_data, - ) - - assert transaction.sender == self.alice.address - assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" - assert transaction.value == 0 - assert transaction.chain_id == "D" - assert transaction.gas_limit == 50000 - assert transaction.gas_price == 1000000000 - assert transaction.nonce == 7 - assert transaction.version == 2 - assert transaction.options == 0 - assert not transaction.data - assert not transaction.guardian - assert not transaction.relayer - assert not transaction.guardian_signature - assert not transaction.relayer_signature - assert ( - transaction.signature.hex() - == "ecf9e9f8d395741c0fbb61eaba74295448ba380dd00a228463e29a278d4e3c9219b2fa54133e32510837e03d7cc9f17738b99b701e148ce098b8340064a5f409" - ) - - def test_create_transfer_transaction(self): - guardian_relayer_data = GuardianRelayerData() - transaction = self.controller.create_transaction( - sender=self.alice, - receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), - native_amount=123456789, - gas_limit=50000, - gas_price=1000000000, - nonce=7, - version=2, - options=0, - guardian_and_relayer_data=guardian_relayer_data, - ) - - assert transaction.sender == self.alice.address - assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" - assert transaction.value == 123456789 - assert transaction.chain_id == "D" - assert transaction.gas_limit == 50000 - assert transaction.gas_price == 1000000000 - assert transaction.nonce == 7 - assert transaction.version == 2 - assert transaction.options == 0 - assert not transaction.data - assert not transaction.guardian - assert not transaction.relayer - assert not transaction.guardian_signature - assert not transaction.relayer_signature - assert ( - transaction.signature.hex() - == "fe0ff59c06c453eed882db562f98c0684a32763a79580a33e269af6edbebc150007118b298f34d17c54b90d55cee76ed1e5254832db8acb2c34ef12f14b8b40d" - ) - - def test_create_transaction_with_data(self): - guardian_relayer_data = GuardianRelayerData() - transaction = self.controller.create_transaction( - sender=self.alice, - receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), - native_amount=0, - gas_limit=50000, - gas_price=1000000000, - nonce=7, - version=2, - options=0, - data="testdata", - guardian_and_relayer_data=guardian_relayer_data, - ) - - assert transaction.sender == self.alice.address - assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" - assert transaction.value == 0 - assert transaction.chain_id == "D" - assert transaction.gas_limit == 50000 - assert transaction.gas_price == 1000000000 - assert transaction.nonce == 7 - assert transaction.version == 2 - assert transaction.options == 0 - assert transaction.data == b"testdata" - assert not transaction.guardian - assert not transaction.relayer - assert not transaction.guardian_signature - assert not transaction.relayer_signature - assert ( - transaction.signature.hex() - == "74bb3bd0c4e87ed64a01456f888236a74b5672cf194a33a2225d868f5f43d65e9149f3145b9431075828ff842f70b977b778895925f37037db979e71563c540c" - ) - - def test_create_guarded_transaction(self): - guardian_relayer_data = GuardianRelayerData( - guardian=Account.new_from_pem(testdata / "testUser2.pem"), - guardian_address=Address.new_from_bech32("erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4"), - ) - - transaction = self.controller.create_transaction( - sender=self.alice, - receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), - native_amount=0, - gas_limit=200000, - gas_price=1000000000, - nonce=7, - version=2, - options=0, - data="testdata", - guardian_and_relayer_data=guardian_relayer_data, - ) - - assert transaction.sender == self.alice.address - assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" - assert transaction.value == 0 - assert transaction.chain_id == "D" - assert transaction.gas_limit == 200000 - assert transaction.gas_price == 1000000000 - assert transaction.nonce == 7 - assert transaction.version == 2 - assert transaction.options == 2 - assert transaction.data == b"testdata" - assert not transaction.relayer - assert not transaction.relayer_signature - assert ( - transaction.guardian - and transaction.guardian.to_bech32() == "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" - ) - assert ( - transaction.guardian_signature.hex() - == "c8b186f85c6e79e157aac48ec55d3f915abbc396e257d30aff193135485395c4b026e7921853366a16bcbbb8dbe5f6cf98a917a8ca11ccdc3f29f5a5a7d7af0c" - ) - assert ( - transaction.signature.hex() - == "8e9338de8f1d66bc6cd2f8e42cb62b5456d82ba66fdf9b8ace750ab3fa697f348084b41996b19d172b627b0b0a312ef76d29191d627b4d9959a3bf17409f780b" - ) - - def test_create_relayed_transaction(self): - guardian_relayer_data = GuardianRelayerData( - relayer=Account.new_from_pem(testdata / "testUser2.pem"), - relayer_address=Address.new_from_bech32("erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4"), - ) - - transaction = self.controller.create_transaction( - sender=self.alice, - receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), - native_amount=0, - gas_limit=200000, - gas_price=1000000000, - nonce=7, - version=2, - options=0, - data="testdata", - guardian_and_relayer_data=guardian_relayer_data, - ) - - assert transaction.sender == self.alice.address - assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" - assert transaction.value == 0 - assert transaction.chain_id == "D" - assert transaction.gas_limit == 200000 - assert transaction.gas_price == 1000000000 - assert transaction.nonce == 7 - assert transaction.version == 2 - assert transaction.options == 0 - assert transaction.data == b"testdata" - assert not transaction.guardian - assert not transaction.guardian_signature - assert ( - transaction.relayer - and transaction.relayer.to_bech32() == "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" - ) - assert ( - transaction.relayer_signature.hex() - == "385fb15816d52118b97a20451c2b225a81ce9be130ad13987453cab36e858f79af0473effc845bef1537ad9f878001e6fcdfeefa36c46c5e8bb6aab83c9b2a0b" - ) - assert ( - transaction.signature.hex() - == "89c2de2939bdd4ed2bdaf8f859f9020b6f7510b3a7298daaebdef53ce1de588181e1c27f8933745927e610ebfe6c41a8875e2b052a48fa22464903f3821b830e" - ) - - def test_create_guarded_relayed_transaction(self): - guardian_relayer_data = GuardianRelayerData( - guardian=Account.new_from_pem(testdata / "testUser.pem"), - guardian_address=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), - relayer=Account.new_from_pem(testdata / "testUser2.pem"), - relayer_address=Address.new_from_bech32("erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4"), - ) - - transaction = self.controller.create_transaction( - sender=self.alice, - receiver=Address.new_from_bech32("erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"), - native_amount=0, - gas_limit=200000, - gas_price=1000000000, - nonce=7, - version=2, - options=0, - data="testdata", - guardian_and_relayer_data=guardian_relayer_data, - ) - - assert transaction.sender == self.alice.address - assert transaction.receiver.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" - assert transaction.value == 0 - assert transaction.chain_id == "D" - assert transaction.gas_limit == 200000 - assert transaction.gas_price == 1000000000 - assert transaction.nonce == 7 - assert transaction.version == 2 - assert transaction.options == 2 - assert transaction.data == b"testdata" - - assert ( - transaction.guardian - and transaction.guardian.to_bech32() == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5" - ) - assert ( - transaction.guardian_signature.hex() - == "06bc457b510cae975e1cc6ab863ed765aadbd948ad23cbd204d66a2b92bee683922d308148af47e28d186bceba13474c79f16b26803c65c908f1d4a2e2ac6a0e" - ) - - assert ( - transaction.relayer - and transaction.relayer.to_bech32() == "erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" - ) - assert ( - transaction.relayer_signature.hex() - == "86e2460a6045ac3142c23f06c3b31fc132f38227faa25ffc8f7e9ce32c4542f6ed37d740d3c83c4454f2390befd223b968d4767c28991f574689b36faa978209" - ) - - assert ( - transaction.signature.hex() - == "b0bce606f2cfd52d6641f8e96aa29dcfd334af4707d88f81dba01dac67e72a99d7143dccadc8d120b387c4eaac84dc01865e57488a2162480a0f15acf50b6306" - ) diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index d5f9503f..f4229b51 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -2,22 +2,9 @@ import logging from typing import Optional, Protocol, TextIO, Union -from multiversx_sdk import ( - Address, - AwaitingOptions, - GasLimitEstimator, - TokenTransfer, - Transaction, - TransactionOnNetwork, - TransactionsFactoryConfig, - TransferTransactionsFactory, -) +from multiversx_sdk import AwaitingOptions, Transaction, TransactionOnNetwork from multiversx_sdk_cli import errors -from multiversx_sdk_cli.base_transactions_controller import BaseTransactionsController -from multiversx_sdk_cli.constants import MIN_GAS_LIMIT -from multiversx_sdk_cli.guardian_relayer_data import GuardianRelayerData -from multiversx_sdk_cli.interfaces import IAccount logger = logging.getLogger("transactions") @@ -38,67 +25,6 @@ def await_transaction_completed(self, transaction_hash: Union[bytes, str], optio # fmt: on -class TransactionsController(BaseTransactionsController): - def __init__(self, chain_id: str, gas_limit_estimator: Optional[GasLimitEstimator] = None) -> None: - config = TransactionsFactoryConfig(chain_id) - self.chain_id = chain_id - self.factory = TransferTransactionsFactory(config, gas_limit_estimator) - - def create_transaction( - self, - sender: IAccount, - receiver: Address, - native_amount: int, - gas_limit: Union[int, None], - gas_price: int, - nonce: int, - version: int, - options: int, - guardian_and_relayer_data: GuardianRelayerData, - token_transfers: Optional[list[TokenTransfer]] = None, - data: Optional[str] = None, - ) -> Transaction: - # if no value, token transfers or data provided, create plain transaction - if not native_amount and not token_transfers and not data: - transaction = Transaction( - sender=sender.address, - receiver=receiver, - gas_limit=MIN_GAS_LIMIT, - chain_id=self.chain_id, - ) - else: - transaction = self.factory.create_transaction_for_transfer( - sender=sender.address, - receiver=receiver, - native_amount=native_amount, - token_transfers=token_transfers, - data=data.encode() if data else None, - ) - - transaction.gas_price = gas_price - transaction.nonce = nonce - transaction.version = version - transaction.options = options - transaction.guardian = guardian_and_relayer_data.guardian_address - transaction.relayer = guardian_and_relayer_data.relayer_address - - self.add_extra_gas_limit_if_required(transaction) - - if gas_limit: - transaction.gas_limit = gas_limit - - self.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, - ) - - return transaction - - def send_and_wait_for_result(transaction: Transaction, proxy: INetworkProvider, timeout: int) -> TransactionOnNetwork: if not transaction.signature: raise errors.TransactionIsNotSigned()