Skip to content

Refactored smart contract interactions #476

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

Merged
merged 2 commits into from
Feb 5, 2025
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
118 changes: 78 additions & 40 deletions multiversx_sdk_cli/cli_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import os
from pathlib import Path
from typing import Any
from typing import Any, Union

from multiversx_sdk import (
Address,
Expand All @@ -21,8 +21,8 @@
from multiversx_sdk_cli.contracts import SmartContract
from multiversx_sdk_cli.cosign_transaction import cosign_transaction
from multiversx_sdk_cli.docker import is_docker_installed, run_docker
from multiversx_sdk_cli.errors import DockerMissingError, NoWalletProvided
from multiversx_sdk_cli.interfaces import IAddress
from multiversx_sdk_cli.errors import DockerMissingError
from multiversx_sdk_cli.interfaces import IAccount
from multiversx_sdk_cli.ux import show_warning

logger = logging.getLogger("cli.contracts")
Expand All @@ -32,15 +32,10 @@ def setup_parser(args: list[str], subparsers: Any) -> Any:
parser = cli_shared.add_group_subparser(
subparsers,
"contract",
"Build, deploy, upgrade and interact with Smart Contracts",
"Deploy, upgrade and interact with Smart Contracts",
)
subparsers = parser.add_subparsers()

sub = cli_shared.add_command_subparser(
subparsers, "contract", "build", "Build a Smart Contract project. This command is DISABLED."
)
sub.set_defaults(func=build)

output_description = CLIOutputBuilder.describe(
with_contract=True, with_transaction_on_network=True, with_simulation=True
)
Expand Down Expand Up @@ -72,6 +67,7 @@ def setup_parser(args: list[str], subparsers: Any) -> Any:
)
cli_shared.add_broadcast_args(sub)
cli_shared.add_guardian_wallet_args(args, sub)
cli_shared.add_relayed_v3_wallet_args(args, sub)

sub.set_defaults(func=deploy)

Expand Down Expand Up @@ -103,6 +99,7 @@ def setup_parser(args: list[str], subparsers: Any) -> Any:
)
cli_shared.add_broadcast_args(sub)
cli_shared.add_guardian_wallet_args(args, sub)
cli_shared.add_relayed_v3_wallet_args(args, sub)

sub.set_defaults(func=call)

Expand Down Expand Up @@ -134,6 +131,7 @@ def setup_parser(args: list[str], subparsers: Any) -> Any:
)
cli_shared.add_broadcast_args(sub)
cli_shared.add_guardian_wallet_args(args, sub)
cli_shared.add_relayed_v3_wallet_args(args, sub)

sub.set_defaults(func=upgrade)

Expand Down Expand Up @@ -205,6 +203,11 @@ def setup_parser(args: list[str], subparsers: Any) -> Any:
)
sub.set_defaults(func=do_reproducible_build)

sub = cli_shared.add_command_subparser(
subparsers, "contract", "build", "Build a Smart Contract project. This command is DISABLED."
)
sub.set_defaults(func=build)

parser.epilog = cli_shared.build_group_epilog(subparsers)
return subparsers

Expand Down Expand Up @@ -274,7 +277,7 @@ def _add_contract_arg(sub: Any):


def _add_contract_abi_arg(sub: Any):
sub.add_argument("--abi", type=str, help="the ABI of the Smart Contract")
sub.add_argument("--abi", type=str, help="the ABI file of the Smart Contract")


def _add_function_arg(sub: Any):
Expand Down Expand Up @@ -338,9 +341,20 @@ def deploy(args: Any):
cli_shared.check_guardian_and_options_args(args)
cli_shared.check_broadcast_args(args)
cli_shared.prepare_chain_id_in_args(args)
cli_shared.prepare_nonce_in_args(args)

sender = cli_shared.prepare_account(args)

if not args.nonce:
nonce = cli_shared.get_current_nonce_for_address(sender.address, args.proxy)
else:
nonce = int(args.nonce)

guardian = cli_shared.load_guardian_account(args)
guardian_address = cli_shared.get_guardian_address(guardian, args)

relayer = cli_shared.load_relayer_account(args)
relayer_address = cli_shared.get_relayer_address(relayer, args)

config = TransactionsFactoryConfig(args.chain)
abi = Abi.load(Path(args.abi)) if args.abi else None
contract = SmartContract(config, abi)
Expand All @@ -358,45 +372,57 @@ def deploy(args: Any):
payable_by_sc=args.metadata_payable_by_sc,
gas_limit=int(args.gas_limit),
value=int(args.value),
nonce=int(args.nonce),
nonce=nonce,
version=int(args.version),
options=int(args.options),
guardian=args.guardian,
guardian=guardian_address,
relayer=relayer_address,
)
tx = _sign_guarded_tx(args, tx)
tx = _sign_guarded_tx_if_guardian(guardian, args, tx)
_sign_relayed_tx_if_relayer(relayer, tx)

address_computer = AddressComputer(NUMBER_OF_SHARDS)
contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce)
contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=tx.nonce)

logger.info("Contract address: %s", contract_address.to_bech32())
utils.log_explorer_contract_address(args.chain, contract_address.to_bech32())

_send_or_simulate(tx, contract_address, args)


def _sign_guarded_tx(args: Any, tx: Transaction) -> Transaction:
try:
guardian_account = cli_shared.prepare_guardian_account(args)
except NoWalletProvided:
guardian_account = None

if guardian_account:
tx.guardian_signature = bytes.fromhex(guardian_account.sign_transaction(tx))
elif args.guardian:
tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) # type: ignore
def _sign_guarded_tx_if_guardian(guardian: Union[IAccount, None], args: Any, tx: Transaction) -> Transaction:
if guardian:
tx.guardian_signature = bytes.fromhex(guardian.sign_transaction(tx))
Copy link
Contributor

Choose a reason for hiding this comment

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

In Python, I remember sign_transaction() returns bytes, not hex-string:

https://github.com/multiversx/mx-sdk-specs/blob/main/accounts/account.md

Is bytes.fromhex() correct here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

here Account.sign_transaction() returns a string, so yes, bytes.from_hex() is correct.

elif tx.guardian and args.guardian_service_url and args.guardian_2fa_code:
tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code)

return tx


def _sign_relayed_tx_if_relayer(relayer: Union[IAccount, None], tx: Transaction):
if relayer and tx.relayer:
tx.relayer_signature = bytes.fromhex(relayer.sign_transaction(tx))


def call(args: Any):
logger.debug("call")
cli_shared.check_guardian_and_options_args(args)
cli_shared.check_broadcast_args(args)
cli_shared.prepare_chain_id_in_args(args)
cli_shared.prepare_nonce_in_args(args)

sender = cli_shared.prepare_account(args)

if not args.nonce:
nonce = cli_shared.get_current_nonce_for_address(sender.address, args.proxy)
else:
nonce = int(args.nonce)

guardian = cli_shared.load_guardian_account(args)
guardian_address = cli_shared.get_guardian_address(guardian, args)

relayer = cli_shared.load_relayer_account(args)
relayer_address = cli_shared.get_relayer_address(relayer, args)

config = TransactionsFactoryConfig(args.chain)
abi = Abi.load(Path(args.abi)) if args.abi else None
contract = SmartContract(config, abi)
Expand All @@ -413,12 +439,14 @@ def call(args: Any):
gas_limit=int(args.gas_limit),
value=int(args.value),
transfers=args.token_transfers,
nonce=int(args.nonce),
nonce=nonce,
version=int(args.version),
options=int(args.options),
guardian=args.guardian,
guardian=guardian_address,
relayer=relayer_address,
)
tx = _sign_guarded_tx(args, tx)
tx = _sign_guarded_tx_if_guardian(guardian, args, tx)
_sign_relayed_tx_if_relayer(relayer, tx)

_send_or_simulate(tx, contract_address, args)

Expand All @@ -428,9 +456,20 @@ def upgrade(args: Any):
cli_shared.check_guardian_and_options_args(args)
cli_shared.check_broadcast_args(args)
cli_shared.prepare_chain_id_in_args(args)
cli_shared.prepare_nonce_in_args(args)

sender = cli_shared.prepare_account(args)

if not args.nonce:
nonce = cli_shared.get_current_nonce_for_address(sender.address, args.proxy)
else:
nonce = int(args.nonce)

guardian = cli_shared.load_guardian_account(args)
guardian_address = cli_shared.get_guardian_address(guardian, args)

relayer = cli_shared.load_relayer_account(args)
relayer_address = cli_shared.get_relayer_address(relayer, args)

config = TransactionsFactoryConfig(args.chain)
abi = Abi.load(Path(args.abi)) if args.abi else None
contract = SmartContract(config, abi)
Expand All @@ -450,24 +489,23 @@ def upgrade(args: Any):
payable_by_sc=args.metadata_payable_by_sc,
gas_limit=int(args.gas_limit),
value=int(args.value),
nonce=int(args.nonce),
nonce=nonce,
version=int(args.version),
options=int(args.options),
guardian=args.guardian,
guardian=guardian_address,
relayer=relayer_address,
)
tx = _sign_guarded_tx(args, tx)
tx = _sign_guarded_tx_if_guardian(guardian, args, tx)
_sign_relayed_tx_if_relayer(relayer, tx)

_send_or_simulate(tx, contract_address, args)


def query(args: Any):
logger.debug("query")

# workaround so we can use the function below to set chainID
args.chain = ""
cli_shared.prepare_chain_id_in_args(args)

factory_config = TransactionsFactoryConfig(args.chain)
# we don't need chainID to query a contract; we use the provided proxy
factory_config = TransactionsFactoryConfig("")
abi = Abi.load(Path(args.abi)) if args.abi else None
contract = SmartContract(factory_config, abi)

Expand Down Expand Up @@ -504,14 +542,14 @@ def _get_contract_arguments(args: Any) -> tuple[list[Any], bool]:
return args.arguments, True


def _send_or_simulate(tx: Transaction, contract_address: IAddress, args: Any):
def _send_or_simulate(tx: Transaction, contract_address: Address, args: Any):
output_builder = cli_shared.send_or_simulate(tx, args, dump_output=False)
output_builder.set_contract_address(contract_address)
utils.dump_out_json(output_builder.build(), outfile=args.outfile)


def verify(args: Any) -> None:
contract = Address.from_bech32(args.contract)
contract = Address.new_from_bech32(args.contract)
verifier_url = args.verifier_url

packaged_src = Path(args.packaged_src).expanduser().resolve()
Expand Down
1 change: 1 addition & 0 deletions multiversx_sdk_cli/cli_delegation.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ def _add_common_arguments(args: List[str], sub: Any):
cli_shared.add_broadcast_args(sub)
cli_shared.add_outfile_arg(sub, what="signed transaction, hash")
cli_shared.add_guardian_wallet_args(args, sub)
cli_shared.add_relayed_v3_wallet_args(args, sub)


def ensure_arguments_are_provided_and_prepared(args: Any):
Expand Down
1 change: 1 addition & 0 deletions multiversx_sdk_cli/cli_dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any:
cli_shared.add_proxy_arg(sub)
cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False)
cli_shared.add_guardian_wallet_args(args, sub)
cli_shared.add_relayed_v3_wallet_args(args, sub)
sub.add_argument("--name", help="the name to register")
sub.set_defaults(func=register)

Expand Down
7 changes: 7 additions & 0 deletions multiversx_sdk_cli/cli_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ def load_guardian_password(args: Any) -> str:
with open(args.guardian_passfile) as pass_file:
return pass_file.read().strip()
return getpass("Keyfile's password: ")


def load_relayer_password(args: Any) -> str:
if args.relayer_passfile:
with open(args.relayer_passfile) as pass_file:
return pass_file.read().strip()
return getpass("Keyfile's password: ")
Loading
Loading