Skip to content

Commit

Permalink
Merge pull request #434 from multiversx/relayed-v3
Browse files Browse the repository at this point in the history
Relayed V3
  • Loading branch information
popenta authored Sep 18, 2024
2 parents 830ed79 + ff3de4c commit 325a71e
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 77 deletions.
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:
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

0 comments on commit 325a71e

Please sign in to comment.