Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relayed V3 #434

Merged
merged 18 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions multiversx_sdk_cli/cli_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
cli_shared.add_outfile_arg(sub)
cli_shared.add_wallet_args(args, sub)
cli_shared.add_proxy_arg(sub)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_guardian=True)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False)
_add_arguments_arg(sub)
sub.add_argument("--wait-result", action="store_true", default=False,
help="signal to wait for the transaction result - only valid if --send is set")
Expand All @@ -97,7 +97,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
cli_shared.add_outfile_arg(sub)
cli_shared.add_wallet_args(args, sub)
cli_shared.add_proxy_arg(sub)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_guardian=True)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False)
_add_function_arg(sub)
_add_arguments_arg(sub)
_add_token_transfers_args(sub)
Expand All @@ -119,7 +119,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
_add_metadata_arg(sub)
cli_shared.add_wallet_args(args, sub)
cli_shared.add_proxy_arg(sub)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_guardian=True)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False)
_add_arguments_arg(sub)
sub.add_argument("--wait-result", action="store_true", default=False,
help="signal to wait for the transaction result - only valid if --send is set")
Expand Down
2 changes: 1 addition & 1 deletion multiversx_sdk_cli/cli_delegation.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
def _add_common_arguments(args: List[str], sub: Any):
cli_shared.add_proxy_arg(sub)
cli_shared.add_wallet_args(args, sub)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_estimate_gas=True, with_guardian=True)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_estimate_gas=True)
cli_shared.add_broadcast_args(sub, relay=False)
cli_shared.add_outfile_arg(sub, what="signed transaction, hash")
cli_shared.add_guardian_wallet_args(args, sub)
Expand Down
2 changes: 1 addition & 1 deletion multiversx_sdk_cli/cli_dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
cli_shared.add_broadcast_args(sub, relay=True)
cli_shared.add_wallet_args(args, sub)
cli_shared.add_proxy_arg(sub)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_guardian=True)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False)
cli_shared.add_guardian_wallet_args(args, sub)
sub.add_argument("--name", help="the name to register")
sub.set_defaults(func=register)
Expand Down
18 changes: 15 additions & 3 deletions multiversx_sdk_cli/cli_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ def add_command_subparser(subparsers: Any, group: str, command: str, description
)


def add_tx_args(args: List[str], sub: Any, with_nonce: bool = True, with_receiver: bool = True, with_data: bool = True, with_estimate_gas: bool = False, with_guardian: bool = False):
def add_tx_args(
args: List[str],
sub: Any,
with_nonce: bool = True,
with_receiver: bool = True,
with_data: bool = True,
with_estimate_gas: bool = False):
if with_nonce:
sub.add_argument("--nonce", type=int, required=not ("--recall-nonce" in args), help="# the nonce for the transaction")
sub.add_argument("--recall-nonce", action="store_true", default=False, help="⭮ whether to recall the nonce when creating the transaction (default: %(default)s)")
Expand All @@ -85,12 +91,18 @@ def add_tx_args(args: List[str], sub: Any, with_nonce: bool = True, with_receive
sub.add_argument("--chain", help="the chain identifier")
sub.add_argument("--version", type=int, default=DEFAULT_TX_VERSION, help="the transaction version (default: %(default)s)")

if with_guardian:
add_guardian_args(sub)
add_guardian_args(sub)
add_relayed_v3_args(sub)

sub.add_argument("--options", type=int, default=0, help="the transaction options (default: 0)")


def add_relayed_v3_args(sub: Any):
sub.add_argument("--relayer", help="the address of the relayer")
sub.add_argument("--inner-transactions", help="a json file containing the inner transactions; should only be provided when creating the relayer's transaction")
sub.add_argument("--inner-transactions-outfile", type=str, help="where to save the transaction as an inner transaction (default: stdout)")


def add_guardian_args(sub: Any):
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="")
Expand Down
43 changes: 40 additions & 3 deletions multiversx_sdk_cli/cli_transactions.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import logging
from pathlib import Path
from typing import Any, List
from typing import Any, Dict, List

from multiversx_sdk import Transaction, TransactionsConverter

from multiversx_sdk_cli import cli_shared, utils
from multiversx_sdk_cli.cli_output import CLIOutputBuilder
from multiversx_sdk_cli.cosign_transaction import cosign_transaction
from multiversx_sdk_cli.custom_network_provider import CustomNetworkProvider
from multiversx_sdk_cli.errors import NoWalletProvided
from multiversx_sdk_cli.errors import BadUsage, NoWalletProvided
from multiversx_sdk_cli.transactions import (compute_relayed_v1_data,
do_prepare_transaction,
load_inner_transactions_from_file,
load_transaction_from_file)

logger = logging.getLogger("cli.transactions")


def setup_parser(args: List[str], subparsers: Any) -> Any:
parser = cli_shared.add_group_subparser(subparsers, "tx", "Create and broadcast Transactions")
Expand Down Expand Up @@ -58,7 +64,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:

def _add_common_arguments(args: List[str], sub: Any):
cli_shared.add_wallet_args(args, sub)
cli_shared.add_tx_args(args, sub, with_guardian=True)
cli_shared.add_tx_args(args, sub)
sub.add_argument("--data-file", type=str, default=None, help="a file containing transaction data")


Expand All @@ -79,15 +85,46 @@ def create_transaction(args: Any):
if args.data_file:
args.data = Path(args.data_file).read_text()

check_relayer_transaction_with_data_field_for_relayed_v3(args)

tx = do_prepare_transaction(args)

if hasattr(args, "inner_transactions_outfile") and args.inner_transactions_outfile:
save_transaction_to_inner_transactions_file(tx, args)
return

if hasattr(args, "relay") and args.relay:
logger.warning("RelayedV1 transactions are deprecated. Please use RelayedV3 instead.")
args.outfile.write(compute_relayed_v1_data(tx))
return

cli_shared.send_or_simulate(tx, args)


def save_transaction_to_inner_transactions_file(transaction: Transaction, args: Any):
inner_txs_file = Path(args.inner_transactions_outfile).expanduser()
transactions = get_inner_transactions_if_any(inner_txs_file)
transactions.append(transaction)

tx_converter = TransactionsConverter()
inner_transactions: Dict[str, Any] = {}
inner_transactions["innerTransactions"] = [tx_converter.transaction_to_dictionary(tx) for tx in transactions]

with open(inner_txs_file, "w") as file:
utils.dump_out_json(inner_transactions, file)


def get_inner_transactions_if_any(file: Path) -> List[Transaction]:
if file.is_file():
return load_inner_transactions_from_file(file)
return []


def check_relayer_transaction_with_data_field_for_relayed_v3(args: Any):
if hasattr(args, "inner_transactions") and args.inner_transactions and args.data:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a double check - is this an invalid case (wrt. to the Protocol)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, we can't set data field for the relayer's transaction.

raise BadUsage("Can't set data field when creating a relayedV3 transaction")


def send_transaction(args: Any):
args = utils.as_object(args)

Expand Down
2 changes: 1 addition & 1 deletion multiversx_sdk_cli/cli_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
def _add_common_arguments(args: List[str], sub: Any):
cli_shared.add_proxy_arg(sub)
cli_shared.add_wallet_args(args, sub)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_estimate_gas=True, with_guardian=True)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_estimate_gas=True)
cli_shared.add_broadcast_args(sub, relay=False)
cli_shared.add_outfile_arg(sub, what="signed transaction, hash")
cli_shared.add_guardian_wallet_args(args, sub)
Expand Down
7 changes: 6 additions & 1 deletion multiversx_sdk_cli/interfaces.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, Protocol
from typing import Any, Dict, Protocol, Sequence


class IAddress(Protocol):
Expand All @@ -25,6 +25,11 @@ class ITransaction(Protocol):
guardian: str
signature: bytes
guardian_signature: bytes
relayer: str

@property
def inner_transactions(self) -> Sequence["ITransaction"]:
...


class IAccount(Protocol):
Expand Down
92 changes: 91 additions & 1 deletion multiversx_sdk_cli/tests/test_cli_transactions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import json
import os
from pathlib import Path
from typing import Any
from typing import Any, List

from multiversx_sdk_cli.cli import main

testdata_path = Path(__file__).parent / "testdata"
testdata_out = Path(__file__).parent / "testdata-out"


def test_relayed_v1_transaction(capsys: Any):
Expand Down Expand Up @@ -87,5 +89,93 @@ def test_create_multi_transfer_transaction(capsys: Any):
assert signature == "575b029d52ff5ffbfb7bab2f04052de88a6f7d022a6ad368459b8af9acaed3717d3f95db09f460649a8f405800838bc2c432496bd03c9039ea166bd32b84660e"


def test_create_and_save_inner_transaction():
return_code = main([
"tx", "new",
"--pem", str(testdata_path / "alice.pem"),
"--receiver", "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
"--nonce", "77",
"--gas-limit", "500000",
"--relayer", "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8",
"--inner-transactions-outfile", str(testdata_out / "inner_transactions.json"),
"--chain", "T",
])
assert False if return_code else True
assert Path(testdata_out / "inner_transactions.json").is_file()


def test_create_and_append_inner_transaction():
return_code = main([
"tx", "new",
"--pem", str(testdata_path / "alice.pem"),
"--receiver", "erd1fggp5ru0jhcjrp5rjqyqrnvhr3sz3v2e0fm3ktknvlg7mcyan54qzccnan",
"--nonce", "1234",
"--gas-limit", "50000",
"--relayer", "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8",
"--inner-transactions-outfile", str(testdata_out / "inner_transactions.json"),
"--chain", "T",
])
assert False if return_code else True

with open(testdata_out / "inner_transactions.json", "r") as file:
json_file = json.load(file)

inner_txs: List[Any] = json_file["innerTransactions"]
assert len(inner_txs) == 2


def test_create_invalid_relayed_transaction():
return_code = main([
"tx", "new",
"--pem", str(testdata_path / "testUser.pem"),
"--receiver", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5",
"--nonce", "987",
"--gas-limit", "5000000",
"--inner-transactions", str(testdata_out / "inner_transactions.json"),
"--data", "test data",
"--chain", "T",
])
assert return_code


def test_create_relayer_transaction(capsys: Any):
return_code = main([
"tx", "new",
"--pem", str(testdata_path / "testUser.pem"),
"--receiver", "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5",
"--nonce", "987",
"--gas-limit", "5000000",
"--inner-transactions", str(testdata_out / "inner_transactions.json"),
"--chain", "T",
])
# remove test file to ensure consistency when running test file locally
os.remove(testdata_out / "inner_transactions.json")

assert False if return_code else True

tx = _read_stdout(capsys)
tx_json = json.loads(tx)["emittedTransaction"]

assert tx_json["sender"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
assert tx_json["receiver"] == "erd1cqqxak4wun7508e0yj9ng843r6hv4mzd0hhpjpsejkpn9wa9yq8sj7u2u5"
assert tx_json["gasLimit"] == 5000000
assert tx_json["nonce"] == 987
assert tx_json["chainID"] == "T"

# should be the two inner transactions created in the tests above
inner_transactions = tx_json["innerTransactions"]
assert len(inner_transactions) == 2

assert inner_transactions[0]["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"
assert inner_transactions[0]["receiver"] == "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"
assert inner_transactions[0]["nonce"] == 77
assert inner_transactions[0]["relayer"] == "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"

assert inner_transactions[1]["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"
assert inner_transactions[1]["receiver"] == "erd1fggp5ru0jhcjrp5rjqyqrnvhr3sz3v2e0fm3ktknvlg7mcyan54qzccnan"
assert inner_transactions[1]["nonce"] == 1234
assert inner_transactions[1]["relayer"] == "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"


def _read_stdout(capsys: Any) -> str:
return capsys.readouterr().out.strip()
Loading
Loading