diff --git a/.github/workflows/release.yml b/.github/release.yml similarity index 100% rename from .github/workflows/release.yml rename to .github/release.yml diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 5b34d106..ecb3a06d 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -3,20 +3,21 @@ from pathlib import Path from typing import Any, List -from multiversx_sdk_core import Address, Transaction +from multiversx_sdk_core import Address, AddressComputer, Transaction +from multiversx_sdk_core.transaction_factories import TransactionsFactoryConfig from multiversx_sdk_network_providers.proxy_network_provider import \ ProxyNetworkProvider -from multiversx_sdk_cli import cli_shared, errors, projects, utils -from multiversx_sdk_cli.accounts import Account, LedgerAccount +from multiversx_sdk_cli import cli_shared, projects, utils from multiversx_sdk_cli.cli_output import CLIOutputBuilder -from multiversx_sdk_cli.cli_password import load_password +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 CodeMetadata, SmartContract +from multiversx_sdk_cli.contracts import SmartContract, query_contract 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.projects.core import get_project_paths_recursively from multiversx_sdk_cli.projects.templates import Contract from multiversx_sdk_cli.ux import show_message @@ -95,6 +96,7 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: cli_shared.add_tx_args(args, sub, with_receiver=False, with_data=False, with_guardian=True) _add_function_arg(sub) _add_arguments_arg(sub) + _add_token_transfers_args(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") sub.add_argument("--timeout", default=100, help="max num of seconds to wait for result" @@ -233,6 +235,12 @@ def _add_arguments_arg(sub: Any): "boolean] or hex-encoded. E.g. --arguments 42 0x64 1000 0xabba str:TOK-a1c2ef true erd1[..]") +def _add_token_transfers_args(sub: Any): + sub.add_argument("--token-transfers", nargs='+', + help="token transfers for transfer & execute, as [token, amount] " + "E.g. --token-transfers NFT-123456-0a 1 ESDT-987654 100000000") + + def _add_metadata_arg(sub: Any): sub.add_argument("--metadata-not-upgradeable", dest="metadata_upgradeable", action="store_false", help="‼ mark the contract as NOT upgradeable (default: upgradeable)") @@ -298,70 +306,23 @@ def deploy(args: Any): logger.debug("deploy") cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - - arguments = args.arguments - gas_price = args.gas_price - gas_limit = args.gas_limit - value = args.value - version = args.version - - contract = _prepare_contract(args) - sender = _prepare_sender(args) cli_shared.prepare_chain_id_in_args(args) + cli_shared.prepare_nonce_in_args(args) - tx = contract.deploy(sender, arguments, gas_price, gas_limit, value, args.chain, version, args.guardian, args.options) - tx = _sign_guarded_tx(args, tx) - - 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, args) - - -def _prepare_contract(args: Any) -> SmartContract: - bytecode = utils.read_binary_file(Path(args.bytecode)).hex() - - metadata = CodeMetadata(upgradeable=args.metadata_upgradeable, readable=args.metadata_readable, - payable=args.metadata_payable, payable_by_sc=args.metadata_payable_by_sc) - contract = SmartContract(bytecode=bytecode, metadata=metadata) - return contract - - -def _prepare_sender(args: Any) -> Account: - sender: Account - if args.ledger: - sender = LedgerAccount(account_index=args.ledger_account_index, address_index=args.ledger_address_index) - elif args.pem: - sender = Account(pem_file=args.pem, pem_index=args.pem_index) - elif args.keyfile: - password = load_password(args) - sender = Account(key_file=args.keyfile, password=password) - else: - raise errors.NoWalletProvided() + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config) - sender.nonce = args.nonce - if args.recall_nonce: - sender.sync_nonce(ProxyNetworkProvider(args.proxy)) - - return sender + address_computer = AddressComputer(NUMBER_OF_SHARDS) + contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce) + tx = contract.prepare_deploy_transaction(sender, args) + tx = _sign_guarded_tx(args, tx) -def _prepare_signer(args: Any) -> Account: - sender: Account - if args.ledger: - sender = LedgerAccount( - account_index=args.ledger_account_index, - address_index=args.ledger_address_index, - ) - elif args.pem: - sender = Account(pem_file=args.pem, pem_index=args.pem_index) - elif args.keyfile: - password = load_password(args) - sender = Account(key_file=args.keyfile, password=password) - else: - raise errors.NoWalletProvided() + logger.info("Contract address: %s", contract_address.to_bech32()) + utils.log_explorer_contract_address(args.chain, contract_address.to_bech32()) - return sender + _send_or_simulate(tx, contract_address, args) def _sign_guarded_tx(args: Any, tx: Transaction) -> Transaction: @@ -382,62 +343,58 @@ def call(args: Any): logger.debug("call") cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - - contract_address = args.contract - function = args.function - arguments = args.arguments - gas_price = args.gas_price - gas_limit = args.gas_limit - value = args.value - version = args.version - - contract = SmartContract(Address.from_bech32(contract_address)) - sender = _prepare_sender(args) cli_shared.prepare_chain_id_in_args(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config) + contract_address = Address.new_from_bech32(args.contract) - tx = contract.execute(sender, function, arguments, gas_price, gas_limit, value, args.chain, version, args.guardian, args.options) + tx = contract.prepare_execute_transaction(sender, args) tx = _sign_guarded_tx(args, tx) - _send_or_simulate(tx, contract, args) + _send_or_simulate(tx, contract_address, args) def upgrade(args: Any): logger.debug("upgrade") + cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - - contract_address = args.contract - arguments = args.arguments - gas_price = args.gas_price - gas_limit = args.gas_limit - value = args.value - version = args.version - - contract = _prepare_contract(args) - contract.address = Address.from_bech32(contract_address) - sender = _prepare_sender(args) cli_shared.prepare_chain_id_in_args(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config) + contract_address = Address.new_from_bech32(args.contract) - tx = contract.upgrade(sender, arguments, gas_price, gas_limit, value, args.chain, version, args.guardian, args.options) + tx = contract.prepare_upgrade_transaction(sender, args) tx = _sign_guarded_tx(args, tx) - _send_or_simulate(tx, contract, args) + _send_or_simulate(tx, contract_address, args) def query(args: Any): logger.debug("query") - contract_address = args.contract + # workaround so we can use the function bellow + args.chain = "" + cli_shared.prepare_chain_id_in_args(args) + + contract_address = Address.new_from_bech32(args.contract) + + proxy = ProxyNetworkProvider(args.proxy) function = args.function - arguments = args.arguments + arguments = args.arguments or [] - contract = SmartContract(address=Address.from_bech32(contract_address)) - result = contract.query(ProxyNetworkProvider(args.proxy), function, arguments) + result = query_contract(contract_address, proxy, function, arguments) utils.dump_out_json(result) -def _send_or_simulate(tx: Transaction, contract: SmartContract, args: Any): +def _send_or_simulate(tx: Transaction, contract_address: IAddress, args: Any): output_builder = cli_shared.send_or_simulate(tx, args, dump_output=False) - output_builder.set_contract_address(contract.address) + output_builder.set_contract_address(contract_address) utils.dump_out_json(output_builder.build(), outfile=args.outfile) @@ -447,7 +404,7 @@ def verify(args: Any) -> None: packaged_src = Path(args.packaged_src).expanduser().resolve() - owner = _prepare_signer(args) + owner = cli_shared.prepare_account(args) docker_image = args.docker_image contract_variant = args.contract_variant diff --git a/multiversx_sdk_cli/cli_delegation.py b/multiversx_sdk_cli/cli_delegation.py index 6b781e52..3d76b348 100644 --- a/multiversx_sdk_cli/cli_delegation.py +++ b/multiversx_sdk_cli/cli_delegation.py @@ -1,11 +1,11 @@ from typing import Any, List +from multiversx_sdk_core.transaction_factories import TransactionsFactoryConfig from multiversx_sdk_network_providers.proxy_network_provider import \ ProxyNetworkProvider from multiversx_sdk_cli import cli_shared, errors, utils -from multiversx_sdk_cli.delegation import staking_provider -from multiversx_sdk_cli.transactions import do_prepare_transaction +from multiversx_sdk_cli.delegation import DelegationOperations def setup_parser(args: List[str], subparsers: Any) -> Any: @@ -142,11 +142,14 @@ def _add_common_arguments(args: List[str], sub: Any): def do_create_delegation_contract(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_for_create_new_staking_contract(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + tx = delegation.prepare_transaction_for_new_delegation_contract(sender, args) cli_shared.send_or_simulate(tx, args) @@ -167,119 +170,152 @@ def get_contract_address_by_deploy_tx_hash(args: Any): def add_new_nodes(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_for_add_nodes(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + + tx = delegation.prepare_transaction_for_adding_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def remove_nodes(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_for_remove_nodes(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + tx = delegation.prepare_transaction_for_removing_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def stake_nodes(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_for_stake_nodes(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + tx = delegation.prepare_transaction_for_staking_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def unbond_nodes(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_for_unbond_nodes(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + + tx = delegation.prepare_transaction_for_unbonding_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def unstake_nodes(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_for_unstake_nodes(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + tx = delegation.prepare_transaction_for_unstaking_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def unjail_nodes(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_for_unjail_nodes(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + tx = delegation.prepare_transaction_for_unjailing_nodes(sender, args) cli_shared.send_or_simulate(tx, args) def change_service_fee(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_change_service_fee(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + + tx = delegation.prepare_transaction_for_changing_service_fee(sender, args) cli_shared.send_or_simulate(tx, args) def modify_delegation_cap(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_modify_delegation_cap(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + tx = delegation.prepare_transaction_for_modifying_delegation_cap(sender, args) cli_shared.send_or_simulate(tx, args) def automatic_activation(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_automatic_activation(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + tx = delegation.prepare_transaction_for_automatic_activation(sender, args) cli_shared.send_or_simulate(tx, args) def redelegate_cap(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_redelegate_cap(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + + tx = delegation.prepare_transaction_for_redelegate_cap(sender, args) cli_shared.send_or_simulate(tx, args) def set_metadata(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) - staking_provider.prepare_args_set_metadata(args) - tx = do_prepare_transaction(args) + cli_shared.prepare_nonce_in_args(args) + + sender = cli_shared.prepare_account(args) + config = TransactionsFactoryConfig(args.chain) + delegation = DelegationOperations(config) + tx = delegation.prepare_transaction_for_setting_metadata(sender, args) cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/cli_output.py b/multiversx_sdk_cli/cli_output.py index e56a3758..91560bfd 100644 --- a/multiversx_sdk_cli/cli_output.py +++ b/multiversx_sdk_cli/cli_output.py @@ -3,12 +3,11 @@ from collections import OrderedDict from typing import Any, Dict, List, Optional, Union -from multiversx_sdk_core import Address from multiversx_sdk_network_providers.transactions import \ transaction_to_dictionary from multiversx_sdk_cli import utils -from multiversx_sdk_cli.interfaces import ITransaction +from multiversx_sdk_cli.interfaces import IAddress, ITransaction from multiversx_sdk_cli.utils import ISerializable logger = logging.getLogger("cli.output") @@ -19,7 +18,7 @@ def __init__(self) -> None: self.emitted_transaction_hash: Optional[str] = None self.emitted_transaction: Union[ITransaction, None] = None self.emitted_transaction_omitted_fields: List[str] = [] - self.contract_address: Union[Address, None] = None + self.contract_address: Union[IAddress, None] = None self.transaction_on_network: Union[ISerializable, None] = None self.transaction_on_network_omitted_fields: List[str] = [] self.simulation_results: Union[ISerializable, None] = None @@ -33,7 +32,7 @@ def set_emitted_transaction(self, emitted_transaction: ITransaction, omitted_fie self.emitted_transaction_omitted_fields = omitted_fields return self - def set_contract_address(self, contract_address: Address): + def set_contract_address(self, contract_address: IAddress): self.contract_address = contract_address return self diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index 1a7f970f..02005c06 100644 --- a/multiversx_sdk_cli/cli_shared.py +++ b/multiversx_sdk_cli/cli_shared.py @@ -10,7 +10,7 @@ ProxyNetworkProvider from multiversx_sdk_cli import config, errors, utils -from multiversx_sdk_cli.accounts import Account +from multiversx_sdk_cli.accounts import Account, LedgerAccount from multiversx_sdk_cli.cli_output import CLIOutputBuilder from multiversx_sdk_cli.cli_password import (load_guardian_password, load_password) @@ -93,9 +93,9 @@ def add_tx_args(args: List[str], sub: Any, with_nonce: bool = True, with_receive def add_guardian_args(sub: Any): - sub.add_argument("--guardian", type=str, help="the address of the guradian") - sub.add_argument("--guardian-service-url", type=str, help="the url of the guardian service") - sub.add_argument("--guardian-2fa-code", type=str, help="the 2fa code for the guardian") + 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="") + sub.add_argument("--guardian-2fa-code", type=str, help="the 2fa code for the guardian", default="") def add_wallet_args(args: List[str], sub: Any): @@ -150,8 +150,7 @@ def prepare_account(args: Any): password = load_password(args) account = Account(key_file=args.keyfile, password=password) elif args.ledger: - address = do_get_ledger_address(account_index=args.ledger_account_index, address_index=args.ledger_address_index) - account = Account(address=Address.new_from_bech32(address)) + account = LedgerAccount(account_index=args.ledger_account_index, address_index=args.ledger_address_index) else: raise errors.NoWalletProvided() diff --git a/multiversx_sdk_cli/cli_transactions.py b/multiversx_sdk_cli/cli_transactions.py index 90f8cf8e..6850ac8e 100644 --- a/multiversx_sdk_cli/cli_transactions.py +++ b/multiversx_sdk_cli/cli_transactions.py @@ -68,8 +68,8 @@ def create_transaction(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_nonce_in_args(args) cli_shared.prepare_chain_id_in_args(args) + cli_shared.prepare_nonce_in_args(args) if args.data_file: args.data = Path(args.data_file).read_text() diff --git a/multiversx_sdk_cli/constants.py b/multiversx_sdk_cli/constants.py index 0e92e53a..fabd8809 100644 --- a/multiversx_sdk_cli/constants.py +++ b/multiversx_sdk_cli/constants.py @@ -11,3 +11,5 @@ DEFAULT_HRP = "erd" ADDRESS_ZERO_BECH32 = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu" + +NUMBER_OF_SHARDS = 3 diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 6575ce77..13381008 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -1,14 +1,18 @@ import base64 import logging -from typing import Any, List, Optional, Protocol, Sequence, Tuple - -from multiversx_sdk_core import Transaction, TransactionPayload -from multiversx_sdk_core.address import Address, AddressComputer +from pathlib import Path +from typing import Any, List, Optional, Protocol, Sequence + +from multiversx_sdk_core import (Token, TokenComputer, TokenTransfer, + Transaction, TransactionPayload) +from multiversx_sdk_core.address import Address +from multiversx_sdk_core.transaction_factories import \ + SmartContractTransactionsFactory from multiversx_sdk_network_providers.interface import IAddress, IContractQuery -from multiversx_sdk_cli import config, constants, errors -from multiversx_sdk_cli.accounts import Account, EmptyAddress -from multiversx_sdk_cli.constants import ADDRESS_ZERO_BECH32, DEFAULT_HRP +from multiversx_sdk_cli import errors +from multiversx_sdk_cli.accounts import Account +from multiversx_sdk_cli.constants import DEFAULT_HRP from multiversx_sdk_cli.utils import Object logger = logging.getLogger("contracts") @@ -61,161 +65,149 @@ class IContractQueryResponse(Protocol): return_message: str -class SmartContract: - def __init__(self, address: Optional[IAddress] = EmptyAddress(), bytecode=None, metadata=None): - self.address = address - self.bytecode = bytecode - self.metadata = metadata or CodeMetadata() - - def deploy(self, owner: Account, arguments: List[Any], gas_price: int, gas_limit: int, value: int, chain: str, version: int, guardian: str, options: int) -> Transaction: - self.owner = owner - address_computer = AddressComputer(number_of_shards=3) - self.address = address_computer.compute_contract_address(self.owner.address, self.owner.nonce) - - arguments = arguments or [] - gas_price = int(gas_price) - gas_limit = int(gas_limit) - value = value or 0 - - tx = Transaction( - chain_id=chain, - sender=owner.address.to_bech32(), - receiver=ADDRESS_ZERO_BECH32, - gas_limit=gas_limit, - gas_price=gas_price, - nonce=owner.nonce, - amount=value, - data=self.prepare_deploy_transaction_data(arguments).data, - version=version, - options=options - ) +class IConfig(Protocol): + chain_id: str + min_gas_limit: int + gas_limit_per_byte: int - if guardian: - tx.guardian = guardian +class SmartContract: + def __init__(self, config: IConfig): + self._factory = SmartContractTransactionsFactory(config, TokenComputer()) + + def prepare_deploy_transaction(self, owner: Account, args: Any) -> Transaction: + arguments = args.arguments or [] + arguments = prepare_args_for_factory(arguments) + + tx = self._factory.create_transaction_for_deploy( + sender=owner.address, + bytecode=Path(args.bytecode), + gas_limit=int(args.gas_limit), + arguments=arguments, + 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 + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - return tx - - def prepare_deploy_transaction_data(self, arguments: List[Any]) -> TransactionPayload: - tx_data = f"{self.bytecode}@{constants.VM_TYPE_WASM_VM}@{self.metadata.to_hex()}" - for arg in arguments: - tx_data += f"@{_prepare_argument(arg)}" + return tx - return TransactionPayload.from_str(tx_data) + def prepare_execute_transaction(self, caller: Account, args: Any) -> Transaction: + contract_address = Address.new_from_bech32(args.contract) + arguments = args.arguments or [] + arguments = prepare_args_for_factory(arguments) + + value = int(args.value) + transfers = args.token_transfers + token_transfers = self._prepare_token_transfers(transfers) if transfers else [] + + tx = self._factory.create_transaction_for_execute( + sender=caller.address, + contract=contract_address, + function=args.function, + gas_limit=int(args.gas_limit), + arguments=arguments, + native_transfer_amount=value, + token_transfers=token_transfers + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(caller.sign_transaction(tx)) - def execute(self, caller: Account, function: str, arguments: List[str], gas_price: int, gas_limit: int, value: int, chain: str, version: int, guardian: str, options: int) -> Transaction: - self.caller = caller + return tx - arguments = arguments or [] - gas_price = int(gas_price) - gas_limit = int(gas_limit) - value = value or 0 - receiver = self.address if self.address else EmptyAddress() - - tx = Transaction( - chain_id=chain, - sender=caller.address.to_bech32(), - receiver=receiver.to_bech32(), - gas_limit=gas_limit, - gas_price=gas_price, - nonce=caller.nonce, - amount=value, - data=self.prepare_execute_transaction_data(function, arguments).data, - version=version, - options=options + def prepare_upgrade_transaction(self, owner: Account, args: Any): + contract_address = Address.new_from_bech32(args.contract) + arguments = args.arguments or [] + arguments = prepare_args_for_factory(arguments) + + tx = self._factory.create_transaction_for_upgrade( + sender=owner.address, + contract=contract_address, + bytecode=Path(args.bytecode), + gas_limit=int(args.gas_limit), + arguments=arguments, + 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 ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - if guardian: - tx.guardian = guardian - - tx.signature = bytes.fromhex(caller.sign_transaction(tx)) return tx - def prepare_execute_transaction_data(self, function: str, arguments: List[Any]) -> TransactionPayload: - tx_data = function - - for arg in arguments: - tx_data += f"@{_prepare_argument(arg)}" - - return TransactionPayload.from_str(tx_data) - - def upgrade(self, owner: Account, arguments: List[Any], gas_price: int, gas_limit: int, value: int, chain: str, version: int, guardian: str, options: int) -> Transaction: - self.owner = owner - - arguments = arguments or [] - gas_price = int(gas_price or config.DEFAULT_GAS_PRICE) - gas_limit = int(gas_limit) - value = value or 0 - receiver = self.address if self.address else EmptyAddress() - - tx = Transaction( - chain_id=chain, - sender=owner.address.to_bech32(), - receiver=receiver.to_bech32(), - gas_limit=gas_limit, - gas_price=gas_price, - nonce=owner.nonce, - amount=value, - data=self.prepare_upgrade_transaction_data(arguments).data, - version=version, - options=options - ) + def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: + token_computer = TokenComputer() + token_transfers: List[TokenTransfer] = [] - if guardian: - tx.guardian = guardian + for i in range(0, len(transfers) - 1, 2): + identifier = transfers[i] + amount = int(transfers[i + 1]) + nonce = token_computer.extract_nonce_from_extended_identifier(identifier) - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) - return tx + token = Token(identifier, nonce) + transfer = TokenTransfer(token, amount) + token_transfers.append(transfer) - def prepare_upgrade_transaction_data(self, arguments: List[Any]) -> TransactionPayload: - tx_data = f"upgradeContract@{self.bytecode}@{self.metadata.to_hex()}" + return token_transfers - for arg in arguments: - tx_data += f"@{_prepare_argument(arg)}" - return TransactionPayload.from_str(tx_data) +def query_contract( + contract_address: IAddress, + proxy: INetworkProvider, + function: str, + arguments: List[Any], + value: int = 0, + caller: Optional[IAddress] = None +) -> List[Any]: + response_data = query_detailed(contract_address, proxy, function, arguments, value, caller) + return_data = response_data.return_data + return [_interpret_return_data(data) for data in return_data] - def query( - self, - proxy: INetworkProvider, - function: str, - arguments: List[Any], - value: int = 0, - caller: Optional[Address] = None - ) -> List[Any]: - response_data = self.query_detailed(proxy, function, arguments, value, caller) - return_data = response_data.return_data - return [self._interpret_return_data(data) for data in return_data] - def query_detailed(self, proxy: INetworkProvider, function: str, arguments: List[Any], - value: int = 0, caller: Optional[Address] = None) -> Any: - arguments = arguments or [] - # Temporary workaround, until we use sdk-core's serializer. - prepared_arguments = [bytes.fromhex(_prepare_argument(arg)) for arg in arguments] +def query_detailed(contract_address: IAddress, proxy: INetworkProvider, function: str, arguments: List[Any], + value: int = 0, caller: Optional[IAddress] = None) -> Any: + arguments = arguments or [] + # Temporary workaround, until we use sdk-core's serializer. + arguments_hex = [_prepare_argument(arg) for arg in arguments] + prepared_arguments_bytes = [bytes.fromhex(arg) for arg in arguments_hex] - query = ContractQuery(self.address, function, value, prepared_arguments, caller) + query = ContractQuery(contract_address, function, value, prepared_arguments_bytes, caller) - response = proxy.query_contract(query) - # Temporary workaround, until we add "isSuccess" on the response class. - if response.return_code != "ok": - raise RuntimeError(f"Query failed: {response.return_message}") - return response + response = proxy.query_contract(query) + # Temporary workaround, until we add "isSuccess" on the response class. + if response.return_code != "ok": + raise RuntimeError(f"Query failed: {response.return_message}") + return response - def _interpret_return_data(self, data: str) -> Any: - if not data: - return data - try: - as_bytes = base64.b64decode(data) - as_hex = as_bytes.hex() - as_number = _interpret_as_number_if_safely(as_hex) +def _interpret_return_data(data: str) -> Any: + if not data: + return data - result = QueryResult(data, as_hex, as_number) - return result - except Exception: - logger.warn(f"Cannot interpret return data: {data}") - return None + try: + as_bytes = base64.b64decode(data) + as_hex = as_bytes.hex() + as_number = _interpret_as_number_if_safely(as_hex) + + result = QueryResult(data, as_hex, as_number) + return result + except Exception: + logger.warn(f"Cannot interpret return data: {data}") + return None def _interpret_as_number_if_safely(as_hex: str) -> Optional[int]: @@ -232,6 +224,43 @@ def _interpret_as_number_if_safely(as_hex: str) -> Optional[int]: return None +def prepare_execute_transaction_data(function: str, arguments: List[Any]) -> TransactionPayload: + tx_data = function + + for arg in arguments: + tx_data += f"@{_prepare_argument(arg)}" + + return TransactionPayload.from_str(tx_data) + + +def prepare_args_for_factory(arguments: List[str]) -> List[Any]: + args: List[Any] = [] + + for arg in arguments: + if arg.startswith(HEX_PREFIX): + args.append(hex_to_bytes(arg)) + elif arg.isnumeric(): + args.append(int(arg)) + elif arg.startswith(DEFAULT_HRP): + args.append(Address.new_from_bech32(arg)) + elif arg.lower() == FALSE_STR_LOWER: + args.append(False) + elif arg.lower() == TRUE_STR_LOWER: + args.append(True) + elif arg.startswith(STR_PREFIX): + args.append(arg[len(STR_PREFIX):]) + + 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) + + +# only used for contract queries and stake operations def _prepare_argument(argument: Any): as_str = str(argument) as_hex = _to_hex(as_str) @@ -286,29 +315,3 @@ def ensure_even_length(string: str) -> str: if len(string) % 2 == 1: return '0' + string return string - - -def sum_flag_values(flag_value_pairs: List[Tuple[int, bool]]) -> int: - value_sum = 0 - for value, flag in flag_value_pairs: - if flag: - value_sum += value - return value_sum - - -class CodeMetadata: - def __init__(self, upgradeable: bool = True, readable: bool = True, payable: bool = False, payable_by_sc: bool = False): - self.upgradeable = upgradeable - self.readable = readable - self.payable = payable - self.payable_by_sc = payable_by_sc - - def to_hex(self): - flag_value_pairs = [ - (0x01_00, self.upgradeable), - (0x04_00, self.readable), - (0x00_02, self.payable), - (0x00_04, self.payable_by_sc) - ] - metadata_value = sum_flag_values(flag_value_pairs) - return f"{metadata_value:04X}" diff --git a/multiversx_sdk_cli/delegation/__init__.py b/multiversx_sdk_cli/delegation/__init__.py index c422a47f..ecdc1533 100644 --- a/multiversx_sdk_cli/delegation/__init__.py +++ b/multiversx_sdk_cli/delegation/__init__.py @@ -1,23 +1,3 @@ -from multiversx_sdk_cli.delegation.staking_provider import prepare_args_modify_delegation_cap, \ - prepare_args_for_unjail_nodes, \ - prepare_args_for_stake_nodes, \ - prepare_args_for_add_nodes, \ - prepare_args_for_unstake_nodes, \ - prepare_args_for_create_new_staking_contract, \ - prepare_args_for_unbond_nodes, \ - prepare_args_for_remove_nodes, \ - prepare_args_change_service_fee, \ - prepare_args_automatic_activation, \ - prepare_args_set_metadata +from multiversx_sdk_cli.delegation.staking_provider import DelegationOperations -__all__ = ["prepare_args_modify_delegation_cap", - "prepare_args_for_unjail_nodes", - "prepare_args_for_stake_nodes", - "prepare_args_for_add_nodes", - "prepare_args_for_unstake_nodes", - "prepare_args_for_create_new_staking_contract", - "prepare_args_for_unbond_nodes", - "prepare_args_for_remove_nodes", - "prepare_args_change_service_fee", - "prepare_args_automatic_activation", - "prepare_args_set_metadata"] +__all__ = ["DelegationOperations"] diff --git a/multiversx_sdk_cli/delegation/staking_provider.py b/multiversx_sdk_cli/delegation/staking_provider.py index 8ed5936b..cbcb6160 100644 --- a/multiversx_sdk_cli/delegation/staking_provider.py +++ b/multiversx_sdk_cli/delegation/staking_provider.py @@ -1,169 +1,334 @@ -import binascii from pathlib import Path -from typing import Any +from typing import Any, List, Protocol, Tuple from multiversx_sdk_core import Address -from multiversx_sdk_wallet.validator_pem import ValidatorPEM -from multiversx_sdk_wallet.validator_signer import ValidatorSigner +from multiversx_sdk_core.transaction_factories import \ + DelegationTransactionsFactory +from multiversx_sdk_wallet import ValidatorPublicKey -from multiversx_sdk_cli import utils -from multiversx_sdk_cli.accounts import Account +from multiversx_sdk_cli.accounts import Account, LedgerAccount from multiversx_sdk_cli.cli_password import load_password -from multiversx_sdk_cli.config import MetaChainSystemSCsCost -from multiversx_sdk_cli.validators.core import estimate_system_sc_call +from multiversx_sdk_cli.errors import BadUsage +from multiversx_sdk_cli.interfaces import IAddress, ITransaction from multiversx_sdk_cli.validators.validators_file import ValidatorsFile -DELEGATION_MANAGER_SC_ADDRESS = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6" - -def prepare_args_for_create_new_staking_contract(args: Any): - args.data = 'createNewDelegationContract' - args.data += '@' + utils.str_int_to_hex_str(str(args.total_delegation_cap)) - args.data += '@' + utils.str_int_to_hex_str(str(args.service_fee)) - - args.receiver = DELEGATION_MANAGER_SC_ADDRESS - - if args.estimate_gas: - # factor is equals 2 because there 2 delegation manager operations when a contract is created - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.DELEGATION_MANAGER_OPS, factor=2) - - -def prepare_args_for_add_nodes(args: Any): - validators_file_path = Path(args.validators_file).expanduser() - validators_file = ValidatorsFile(validators_file_path) - account = Account() - - # TODO: Refactor, so that only address is received here. - if args.using_delegation_manager: - account = Account(address=Address.new_from_bech32(args.delegation_contract)) - elif args.pem: - account = Account(pem_file=args.pem) - elif args.keyfile: - password = load_password(args) - account = Account(key_file=args.keyfile, password=password) - - add_nodes_data = "addNodes" - num_of_nodes = validators_file.get_num_of_nodes() - for validator in validators_file.get_validators_list(): - # Get path of "pemFile", make it absolute - validator_pem = Path(validator.get("pemFile")).expanduser() - validator_pem = validator_pem if validator_pem.is_absolute() else validators_file_path.parent / validator_pem - - pem_file = ValidatorPEM.from_file(validator_pem) - - validator_signer = ValidatorSigner(pem_file.secret_key) - signed_message = validator_signer.sign(account.address.pubkey).hex() - - add_nodes_data += f"@{pem_file.secret_key.generate_public_key().hex()}@{signed_message}" - - args.receiver = args.delegation_contract - args.data = add_nodes_data - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.DELEGATION_OPS, num_of_nodes + 1) - - -def prepare_args_for_remove_nodes(args: Any): - _prepare_args("removeNodes", args) - - -def prepare_args_for_stake_nodes(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.bls_keys) - args.data = 'stakeNodes' + parsed_keys - args.receiver = args.delegation_contract - - if args.estimate_gas: - cost = MetaChainSystemSCsCost.DELEGATION_OPS + MetaChainSystemSCsCost.STAKE - args.gas_limit = estimate_system_sc_call(args.data, cost, num_keys + 1) - - -def prepare_args_for_unbond_nodes(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.bls_keys) - args.data = 'unBondNodes' + parsed_keys - args.receiver = args.delegation_contract - - if args.estimate_gas: - cost = MetaChainSystemSCsCost.DELEGATION_OPS + MetaChainSystemSCsCost.UNBOND - args.gas_limit = estimate_system_sc_call(args.data, cost, num_keys + 1) - - -def prepare_args_for_unstake_nodes(args: Any): - parsed_keys, num_keys = utils.parse_keys(args.bls_keys) - args.data = 'unStakeNodes' + parsed_keys - args.receiver = args.delegation_contract - - if args.estimate_gas: - cost = MetaChainSystemSCsCost.DELEGATION_OPS + MetaChainSystemSCsCost.UNSTAKE - args.gas_limit = estimate_system_sc_call(args.data, cost, num_keys + 1) - - -def prepare_args_for_unjail_nodes(args: Any): - _prepare_args("unJailNodes", args) - - -def _prepare_args(command: str, args: Any): - parsed_keys, num_keys = utils.parse_keys(args.bls_keys) - args.data = command + parsed_keys - args.receiver = args.delegation_contract - - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.DELEGATION_OPS, num_keys + 1) - - -def prepare_args_change_service_fee(args: Any): - data = 'changeServiceFee' - data += '@' + utils.str_int_to_hex_str(str(args.service_fee)) - - args.data = data - args.receiver = args.delegation_contract - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.DELEGATION_OPS, 1) - - -def prepare_args_modify_delegation_cap(args: Any): - data = 'modifyTotalDelegationCap' - data += '@' + utils.str_int_to_hex_str(str(args.delegation_cap)) - - args.data = data - args.receiver = args.delegation_contract - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.DELEGATION_OPS, 1) - - -def prepare_args_automatic_activation(args: Any): - data = 'setAutomaticActivation' - if args.set: - data += '@' + binascii.hexlify(str.encode('true')).decode() - - if args.unset: - data += '@' + binascii.hexlify(str.encode('false')).decode() - - args.data = data - args.receiver = args.delegation_contract - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.DELEGATION_OPS, 1) - - -def prepare_args_redelegate_cap(args: Any): - data = 'setCheckCapOnReDelegateRewards' - if args.set: - data += '@' + binascii.hexlify(str.encode('true')).decode() - - if args.unset: - data += '@' + binascii.hexlify(str.encode('false')).decode() - - args.data = data - args.receiver = args.delegation_contract - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.DELEGATION_OPS, 1) - - -def prepare_args_set_metadata(args: Any): - data = 'setMetaData' - data += '@' + binascii.hexlify(str.encode(args.name)).decode() - data += '@' + binascii.hexlify(str.encode(args.website)).decode() - data += '@' + binascii.hexlify(str.encode(args.identifier)).decode() - - args.data = data - args.receiver = args.delegation_contract - if args.estimate_gas: - args.gas_limit = estimate_system_sc_call(args.data, MetaChainSystemSCsCost.DELEGATION_OPS, 1) +class IConfig(Protocol): + chain_id: str + min_gas_limit: int + gas_limit_per_byte: int + gas_limit_stake: int + gas_limit_unstake: int + gas_limit_unbond: int + gas_limit_create_delegation_contract: int + gas_limit_delegation_operations: int + additional_gas_limit_per_validator_node: int + additional_gas_for_delegation_operations: int + + +class IAccount(Protocol): + @property + def address(self) -> IAddress: + ... + + nonce: int + + def sign_transaction(self, transaction: ITransaction) -> str: + ... + + +class DelegationOperations: + def __init__(self, config: IConfig) -> None: + self._factory = DelegationTransactionsFactory(config) + + def prepare_transaction_for_new_delegation_contract(self, owner: IAccount, args: Any) -> ITransaction: + tx = self._factory.create_transaction_for_new_delegation_contract( + sender=owner.address, + total_delegation_cap=int(args.total_delegation_cap), + service_fee=int(args.service_fee), + amount=int(args.value) + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_adding_nodes(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + public_keys, signed_messages = self._get_public_keys_and_signed_messages(args) + + tx = self._factory.create_transaction_for_adding_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys, + signed_messages=signed_messages + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_removing_nodes(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + public_keys = self._parse_public_bls_keys(args.bls_keys) + + tx = self._factory.create_transaction_for_removing_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_staking_nodes(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + public_keys = self._parse_public_bls_keys(args.bls_keys) + + tx = self._factory.create_transaction_for_staking_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_unbonding_nodes(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + public_keys = self._parse_public_bls_keys(args.bls_keys) + + tx = self._factory.create_transaction_for_unbonding_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_unstaking_nodes(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + public_keys = self._parse_public_bls_keys(args.bls_keys) + + tx = self._factory.create_transaction_for_unstaking_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_unjailing_nodes(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + public_keys = self._parse_public_bls_keys(args.bls_keys) + + tx = self._factory.create_transaction_for_unjailing_nodes( + sender=owner.address, + delegation_contract=delegation_contract, + public_keys=public_keys + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_changing_service_fee(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + + tx = self._factory.create_transaction_for_changing_service_fee( + sender=owner.address, + delegation_contract=delegation_contract, + service_fee=int(args.service_fee) + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_modifying_delegation_cap(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + + tx = self._factory.create_transaction_for_modifying_delegation_cap( + sender=owner.address, + delegation_contract=delegation_contract, + delegation_cap=int(args.delegation_cap) + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_automatic_activation(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + + if args.set: + tx = self._factory.create_transaction_for_setting_automatic_activation( + sender=owner.address, + delegation_contract=delegation_contract + ) + elif args.unset: + tx = self._factory.create_transaction_for_unsetting_automatic_activation( + sender=owner.address, + delegation_contract=delegation_contract + ) + else: + raise BadUsage("Either `--set` or `--unset` should be provided") + + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_redelegate_cap(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + + if args.set: + tx = self._factory.create_transaction_for_setting_cap_check_on_redelegate_rewards( + sender=owner.address, + delegation_contract=delegation_contract + ) + elif args.unset: + tx = self._factory.create_transaction_for_unsetting_cap_check_on_redelegate_rewards( + sender=owner.address, + delegation_contract=delegation_contract + ) + else: + raise BadUsage("Either `--set` or `--unset` should be provided") + + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def prepare_transaction_for_setting_metadata(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + + tx = self._factory.create_transaction_for_setting_metadata( + sender=owner.address, + delegation_contract=delegation_contract, + name=args.name, + website=args.website, + identifier=args.identifier + ) + tx.nonce = int(args.nonce) + tx.version = int(args.version) + tx.options = int(args.options) + tx.guardian = args.guardian + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + + if args.gas_limit: + tx.gas_limit = int(args.gas_limit) + + return tx + + def _parse_public_bls_keys(self, public_bls_keys: str) -> List[ValidatorPublicKey]: + keys = public_bls_keys.split(",") + validator_public_keys: List[ValidatorPublicKey] = [] + + for key in keys: + validator_public_keys.append(ValidatorPublicKey(bytes.fromhex(key))) + + return validator_public_keys + + def _get_public_keys_and_signed_messages(self, args: Any) -> Tuple[List[ValidatorPublicKey], List[bytes]]: + validators_file_path = Path(args.validators_file).expanduser() + validators_file = ValidatorsFile(validators_file_path) + signers = validators_file.load_signers() + + pubkey = self._get_pubkey_to_be_signed(args) + + public_keys: List[ValidatorPublicKey] = [] + signed_messages: List[bytes] = [] + for signer in signers: + signed_message = signer.sign(pubkey) + + public_keys.append(signer.secret_key.generate_public_key()) + signed_messages.append(signed_message) + + return public_keys, signed_messages + + def _get_pubkey_to_be_signed(self, args: Any) -> bytes: + account = Account() + if args.using_delegation_manager: + account = Account(address=Address.new_from_bech32(args.delegation_contract)) + elif args.pem: + account = Account(pem_file=args.pem) + elif args.keyfile: + password = load_password(args) + account = Account(key_file=args.keyfile, password=password) + elif args.ledger: + account = LedgerAccount(account_index=args.ledger_account_index, address_index=args.ledger_address_index) + + return account.address.get_public_key() diff --git a/multiversx_sdk_cli/dns.py b/multiversx_sdk_cli/dns.py index 1485532a..aca2389d 100644 --- a/multiversx_sdk_cli/dns.py +++ b/multiversx_sdk_cli/dns.py @@ -1,12 +1,12 @@ from typing import Any, List, Protocol from Cryptodome.Hash import keccak -from multiversx_sdk_core.address import Address, AddressComputer +from multiversx_sdk_core import Address, AddressComputer from multiversx_sdk_cli import cli_shared, utils from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.constants import ADDRESS_ZERO_BECH32, DEFAULT_HRP -from multiversx_sdk_cli.contracts import SmartContract +from multiversx_sdk_cli.contracts import query_contract from multiversx_sdk_cli.transactions import ( do_prepare_transaction, tx_to_dictionary_as_inner_for_relayed_V1) @@ -23,8 +23,8 @@ def query_contract(self, query: Any) -> Any: def resolve(name: str, proxy: INetworkProvider) -> Address: name_arg = "0x{}".format(str.encode(name).hex()) dns_address = dns_address_for_name(name) - contract = SmartContract(dns_address) - result = contract.query(proxy, "resolve", [name_arg]) + + result = query_contract(dns_address, proxy, "resolve", [name_arg]) if len(result) == 0: return Address.from_bech32(ADDRESS_ZERO_BECH32) return Address.from_hex(result[0].hex, DEFAULT_HRP) @@ -33,8 +33,8 @@ def resolve(name: str, proxy: INetworkProvider) -> Address: def validate_name(name: str, shard_id: int, proxy: INetworkProvider): name_arg = "0x{}".format(str.encode(name).hex()) dns_address = compute_dns_address_for_shard_id(shard_id) - contract = SmartContract(dns_address) - contract.query(proxy, "validateName", [name_arg]) + + query_contract(dns_address, proxy, "validateName", [name_arg]) def register(args: Any): @@ -69,8 +69,7 @@ def name_hash(name: str) -> bytes: def registration_cost(shard_id: int, proxy: INetworkProvider) -> int: dns_address = compute_dns_address_for_shard_id(shard_id) - contract = SmartContract(dns_address) - result = contract.query(proxy, "getRegistrationCost", []) + result = query_contract(dns_address, proxy, "getRegistrationCost", []) if len(result[0]) == 0: return 0 else: @@ -79,8 +78,7 @@ def registration_cost(shard_id: int, proxy: INetworkProvider) -> int: def version(shard_id: int, proxy: INetworkProvider) -> str: dns_address = compute_dns_address_for_shard_id(shard_id) - contract = SmartContract(dns_address) - result = contract.query(proxy, "version", []) + result = query_contract(dns_address, proxy, "version", []) return bytearray.fromhex(result[0].hex).decode() diff --git a/multiversx_sdk_cli/localnet/genesis.py b/multiversx_sdk_cli/localnet/genesis.py index 90a3e3c6..35f1e37b 100644 --- a/multiversx_sdk_cli/localnet/genesis.py +++ b/multiversx_sdk_cli/localnet/genesis.py @@ -1,6 +1,6 @@ from multiversx_sdk_core.address import Address, AddressComputer -from multiversx_sdk_cli.contracts import SmartContract +from multiversx_sdk_cli.constants import NUMBER_OF_SHARDS from multiversx_sdk_cli.localnet import wallets @@ -10,13 +10,12 @@ def get_owner_of_genesis_contracts(): def get_delegation_address() -> Address: - contract = SmartContract() - contract.owner = get_owner_of_genesis_contracts() - contract.owner.nonce = 0 + owner = get_owner_of_genesis_contracts() + owner.nonce = 0 - address_computer = AddressComputer() - contract.address = address_computer.compute_contract_address(contract.owner.address, contract.owner.nonce) - return contract.address + address_computer = AddressComputer(NUMBER_OF_SHARDS) + address = address_computer.compute_contract_address(owner.address, owner.nonce) + return address def is_last_user(nickname: str) -> bool: diff --git a/multiversx_sdk_cli/tests/test_cli_contracts.py b/multiversx_sdk_cli/tests/test_cli_contracts.py index 259b1a0e..886454e9 100644 --- a/multiversx_sdk_cli/tests/test_cli_contracts.py +++ b/multiversx_sdk_cli/tests/test_cli_contracts.py @@ -1,3 +1,4 @@ +import json from pathlib import Path from typing import Any @@ -165,3 +166,148 @@ def test_contract_call(): ] ) assert Path.is_file(output_file) == True + + +def test_contract_transfer_and_execute(capsys: Any): + contract_address = "erd1qqqqqqqqqqqqqpgqv7sl6ws5dgwe5m04xtg0dvqyu2efz5a6d8ssxn4k9q" + first_token = "NFT-123456-02" + second_token = "ESDT-987654" + + main([ + "contract", "call", contract_address, + "--pem", f"{parent}/testdata/testUser.pem", + "--proxy", "https://devnet-api.multiversx.com", + "--chain", "D", + "--recall-nonce", + "--gas-limit", "5000000", + "--function", "add", + "--arguments", "5", + "--token-transfers", first_token, "1" + ]) + data = get_transaction_data(capsys) + assert data == "ESDTNFTTransfer@4e46542d313233343536@02@01@0000000000000000050067a1fd3a146a1d9a6df532d0f6b004e2b29153ba69e1@616464@05" + + # Clear the captured content + capsys.readouterr() + + main([ + "contract", "call", contract_address, + "--pem", f"{parent}/testdata/testUser.pem", + "--proxy", "https://devnet-api.multiversx.com", + "--chain", "D", + "--recall-nonce", + "--gas-limit", "5000000", + "--function", "add", + "--arguments", "5", + "--token-transfers", first_token, "1", second_token, "100" + ]) + data = get_transaction_data(capsys) + assert data == "MultiESDTNFTTransfer@0000000000000000050067a1fd3a146a1d9a6df532d0f6b004e2b29153ba69e1@02@4e46542d313233343536@02@01@455344542d393837363534@@64@616464@05" + + +def test_contract_flow(capsys: Any): + alice = f"{parent}/testdata/alice.pem" + adder = f"{parent}/testdata/adder.wasm" + + main([ + "contract", "deploy", + "--bytecode", adder, + "--pem", alice, + "--recall-nonce", + "--gas-limit", "5000000", + "--proxy", "https://testnet-api.multiversx.com", + "--arguments", "0", + "--send", "--wait-result" + ]) + contract = get_contract_address(capsys) + + # Clear the captured content + capsys.readouterr() + + main([ + "contract", "query", + contract, + "--function", "getSum", + "--proxy", "https://testnet-api.multiversx.com" + ]) + response = get_query_response(capsys) + assert response == "" + + # Clear the captured content + capsys.readouterr() + + main([ + "contract", "call", + contract, + "--pem", alice, + "--function", "add", + "--recall-nonce", + "--gas-limit", "5000000", + "--proxy", "https://testnet-api.multiversx.com", + "--arguments", "7", + "--send", "--wait-result" + ]) + + # Clear the captured content + capsys.readouterr() + + main([ + "contract", "query", + contract, + "--function", "getSum", + "--proxy", "https://testnet-api.multiversx.com" + ]) + response = get_query_response(capsys) + assert response["number"] == 7 + + # Clear the captured content + capsys.readouterr() + + main([ + "contract", "upgrade", + contract, + "--bytecode", adder, + "--pem", alice, + "--recall-nonce", + "--gas-limit", "5000000", + "--proxy", "https://testnet-api.multiversx.com", + "--arguments", "0", + "--send", "--wait-result" + ]) + + +def test_contract_deploy_without_required_arguments(): + alice = f"{parent}/testdata/alice.pem" + adder = f"{parent}/testdata/adder.wasm" + + return_code = main([ + "contract", "deploy", + "--bytecode", adder, + "--pem", alice, + "--recall-nonce", + "--gas-limit", "5000000", + "--arguments", "0", + "--send", "--wait-result" + ]) + assert return_code + + +def _read_stdout(capsys: Any) -> str: + return capsys.readouterr().out.strip() + + +def get_contract_address(capsys: Any): + out = _read_stdout(capsys) + output = json.loads(out) + return output["contractAddress"] + + +def get_query_response(capsys: Any): + out = _read_stdout(capsys).replace("\n", "").replace(" ", "") + return json.loads(out)[0] + + +def get_transaction_data(capsys: Any): + out = _read_stdout(capsys) + output = json.loads(out) + return output["emittedTransactionData"] diff --git a/multiversx_sdk_cli/tests/test_cli_staking_provider.py b/multiversx_sdk_cli/tests/test_cli_staking_provider.py new file mode 100644 index 00000000..d3bdcc60 --- /dev/null +++ b/multiversx_sdk_cli/tests/test_cli_staking_provider.py @@ -0,0 +1,288 @@ +import json +from pathlib import Path +from typing import Any + +from multiversx_sdk_cli.cli import main + +parent = Path(__file__).parent +alice = parent / "testdata" / "alice.pem" + +first_bls_key = "f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d" +second_bls_key = "1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + + +def test_create_new_delegation_contract(capsys: Any): + main([ + "staking-provider", "create-new-delegation-contract", + "--pem", str(alice), + "--recall-nonce", "--estimate-gas", + "--value", "1250000000000000000000", + "--total-delegation-cap", "10000000000000000000000", + "--service-fee", "100", + "--proxy", "https://testnet-api.multiversx.com" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "createNewDelegationContract@021e19e0c9bab2400000@64" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6" + assert transaction["chainID"] == "T" + assert transaction["gasLimit"] == 60126500 + assert transaction["value"] == "1250000000000000000000" + + +def test_add_nodes(capsys: Any): + validators_file = parent / "testdata" / "validators.json" + + main([ + "staking-provider", "add-nodes", + "--validators-file", str(validators_file), + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "addNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208@604882237a9845f508ad03877b5aab90569683eeb51fafcbbeb87440ba359992b3c0b837a8757c25be18132549404f88@78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d@ec54a009695af56c3585ef623387b67b6df1974b0b3c9138eb64bde6eb33978ae9851112b20c99bf63588e8e949e4388@7188b234a8bf834f2e6258012aa09a2ab93178ffab9c789480275f61fe02cd1b9a58ddc63b79a73abea9e2b7ac5cac0b0d4324eff50aca2f0ec946b9ae6797511fa3ce461b57e77129cba8ab3b51147695d4ce889cbe67905f6586b4e4f22491@c6c637de17db5f89a2fa1d1d935cb60c0e5e8958d3bfc47f903f774dd97398c8fe22093e113865ee98c3afdd1de62694" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + + +def test_remove_nodes(capsys: Any): + main([ + "staking-provider", "remove-nodes", + "--bls-keys", f"{first_bls_key},{second_bls_key}", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "removeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 13645500 + + +def test_stake_nodes(capsys: Any): + main([ + "staking-provider", "stake-nodes", + "--bls-keys", f"{first_bls_key},{second_bls_key}", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "stakeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 18644000 + + +def test_unbond_nodes(capsys: Any): + main([ + "staking-provider", "unbond-nodes", + "--bls-keys", f"{first_bls_key},{second_bls_key}", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "unBondNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 18645500 + + +def test_unstake_nodes(capsys: Any): + main([ + "staking-provider", "unstake-nodes", + "--bls-keys", f"{first_bls_key},{second_bls_key}", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "unStakeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 18647000 + + +def test_unjail_nodes(capsys: Any): + main([ + "staking-provider", "unjail-nodes", + "--bls-keys", f"{first_bls_key},{second_bls_key}", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "unJailNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 13645500 + + +def test_change_service_fee(capsys: Any): + main([ + "staking-provider", "change-service-fee", + "--service-fee", "100", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "changeServiceFee@64" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 11078500 + + +def test_modify_delegation_cap(capsys: Any): + main([ + "staking-provider", "modify-delegation-cap", + "--delegation-cap", "10000000000000000000000", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "modifyTotalDelegationCap@021e19e0c9bab2400000" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 11117500 + + +def test_automatic_activation(capsys: Any): + main([ + "staking-provider", "automatic-activation", + "--set", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "setAutomaticActivation@74727565" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 11096500 + + main([ + "staking-provider", "automatic-activation", + "--unset", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "setAutomaticActivation@66616c7365" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 11099500 + + +def test_redelegate_cap(capsys: Any): + main([ + "staking-provider", "redelegate-cap", + "--set", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "setCheckCapOnReDelegateRewards@74727565" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 11108500 + + main([ + "staking-provider", "redelegate-cap", + "--unset", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "setCheckCapOnReDelegateRewards@66616c7365" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 11111500 + + +def test_set_metadata(capsys: Any): + main([ + "staking-provider", "set-metadata", + "--name", "Test", + "--website", "www.test.com", + "--identifier", "TEST", + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--proxy", "https://testnet-api.multiversx.com", + "--recall-nonce", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "setMetaData@54657374@7777772e746573742e636f6d@54455354" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 11131000 + + +def _read_stdout(capsys: Any) -> str: + return capsys.readouterr().out.strip() + + +def get_transaction(capsys: Any): + output = _read_stdout(capsys) + return json.loads(output) diff --git a/multiversx_sdk_cli/tests/test_cli_validators.sh b/multiversx_sdk_cli/tests/test_cli_validators.sh index dad1eb52..7a355223 100755 --- a/multiversx_sdk_cli/tests/test_cli_validators.sh +++ b/multiversx_sdk_cli/tests/test_cli_validators.sh @@ -9,7 +9,7 @@ testAll() { echo "Stake with recall nonce" ${CLI} --verbose validator stake --pem="${USERS}/alice.pem" --value="2500${DENOMINATION}" --validators-file=./testdata/validators.json --reward-address=${REWARD_ADDRESS} --chain=${CHAIN_ID} --proxy=${PROXY} --estimate-gas --recall-nonce --send || return 1 echo "Stake with provided nonce" - ${CLI} --verbose validator stake --pem="${USERS}/bob.pem" --value="2500${DENOMINATION}" --validators-file=./testdata/validators.json --reward-address=${REWARD_ADDRESS} --chain=${CHAIN_ID} --proxy=${PROXY} --estimate-gas --nonce=300 --send || return 1 + ${CLI} --verbose validator stake --pem="${USERS}/bob.pem" --value="2500${DENOMINATION}" --validators-file=./testdata/validators.json --reward-address=${REWARD_ADDRESS} --chain=${CHAIN_ID} --proxy=${PROXY} --estimate-gas --nonce=300 || return 1 echo "Stake with topUP" diff --git a/multiversx_sdk_cli/tests/test_code_metadata.py b/multiversx_sdk_cli/tests/test_code_metadata.py deleted file mode 100644 index 2cf904a8..00000000 --- a/multiversx_sdk_cli/tests/test_code_metadata.py +++ /dev/null @@ -1,21 +0,0 @@ -from multiversx_sdk_cli.contracts import CodeMetadata - - -def test_code_metadata_defaults(): - assert CodeMetadata().to_hex() == "0500" - assert CodeMetadata().to_hex() == CodeMetadata(upgradeable=True, readable=True, payable=False, payable_by_sc=False).to_hex() - - -def test_code_metadata_single_flag(): - assert CodeMetadata(upgradeable=False, readable=False, payable=False, payable_by_sc=False).to_hex() == "0000" - assert CodeMetadata(upgradeable=True, readable=False, payable=False, payable_by_sc=False).to_hex() == "0100" - assert CodeMetadata(upgradeable=False, readable=True, payable=False, payable_by_sc=False).to_hex() == "0400" - assert CodeMetadata(upgradeable=False, readable=False, payable=True, payable_by_sc=False).to_hex() == "0002" - assert CodeMetadata(upgradeable=False, readable=False, payable=False, payable_by_sc=True).to_hex() == "0004" - - -def test_code_metadata_multiple_flags(): - assert CodeMetadata(upgradeable=False, readable=True, payable=False, payable_by_sc=True).to_hex() == "0404" - assert CodeMetadata(upgradeable=True, readable=True, payable=False, payable_by_sc=False).to_hex() == "0500" - assert CodeMetadata(upgradeable=False, readable=False, payable=True, payable_by_sc=True).to_hex() == "0006" - assert CodeMetadata(upgradeable=True, readable=True, payable=True, payable_by_sc=True).to_hex() == "0506" diff --git a/multiversx_sdk_cli/tests/test_contracts.py b/multiversx_sdk_cli/tests/test_contracts.py index f6eed78d..733969a3 100644 --- a/multiversx_sdk_cli/tests/test_contracts.py +++ b/multiversx_sdk_cli/tests/test_contracts.py @@ -3,15 +3,14 @@ import pytest from Cryptodome.Hash import keccak -from multiversx_sdk_core.address import Address, AddressComputer +from multiversx_sdk_core.address import Address from multiversx_sdk_cli import errors from multiversx_sdk_cli.accounts import Account -from multiversx_sdk_cli.constants import DEFAULT_HRP from multiversx_sdk_cli.contract_verification import _create_request_signature -from multiversx_sdk_cli.contracts import (SmartContract, - _interpret_as_number_if_safely, - _prepare_argument) +from multiversx_sdk_cli.contracts import (_interpret_as_number_if_safely, + _prepare_argument, + prepare_args_for_factory) logging.basicConfig(level=logging.INFO) @@ -23,23 +22,6 @@ def test_playground_keccak(): assert hexhash == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" -def test_compute_address(): - address_computer = AddressComputer() - contract = SmartContract() - contract.owner = Account(address=Address.from_hex("93ee6143cdc10ce79f15b2a6c2ad38e9b6021c72a1779051f47154fd54cfbd5e", DEFAULT_HRP)) - - contract.owner.nonce = 0 - contract.address = address_computer.compute_contract_address(contract.owner.address, contract.owner.nonce) - assert contract.address - assert contract.address.hex() == "00000000000000000500bb652200ed1f994200ab6699462cab4b1af7b11ebd5e" - assert contract.address.bech32() == "erd1qqqqqqqqqqqqqpgqhdjjyq8dr7v5yq9tv6v5vt9tfvd00vg7h40q6779zn" - - contract.owner.nonce = 1 - contract.address = address_computer.compute_contract_address(contract.owner.address, contract.owner.nonce) - assert contract.address.hex() == "000000000000000005006e4f90488e27342f9a46e1809452c85ee7186566bd5e" - assert contract.address.bech32() == "erd1qqqqqqqqqqqqqpgqde8eqjywyu6zlxjxuxqfg5kgtmn3setxh40qen8egy" - - def test_prepare_argument(): assert _prepare_argument('0x5') == '05' assert _prepare_argument('5') == '05' @@ -85,3 +67,19 @@ def test_interpret_as_number_if_safely(): assert _interpret_as_number_if_safely("") == 0 assert _interpret_as_number_if_safely("0x5") == 5 assert _interpret_as_number_if_safely("FF") == 255 + + +def test_prepare_args_for_factories(): + args = [ + "0x5", "123", "false", "true", + "str:test-string", + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + ] + + arguments = prepare_args_for_factory(args) + assert arguments[0] == b"\x05" + assert arguments[1] == 123 + assert arguments[2] == False + assert arguments[3] == True + assert arguments[4] == "test-string" + assert arguments[5].to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" diff --git a/multiversx_sdk_cli/tests/testdata/adder.wasm b/multiversx_sdk_cli/tests/testdata/adder.wasm old mode 100755 new mode 100644 index 820c0f40..ffb39b0d Binary files a/multiversx_sdk_cli/tests/testdata/adder.wasm and b/multiversx_sdk_cli/tests/testdata/adder.wasm differ diff --git a/multiversx_sdk_cli/validators/core.py b/multiversx_sdk_cli/validators/core.py index b19a21ca..61d0c136 100644 --- a/multiversx_sdk_cli/validators/core.py +++ b/multiversx_sdk_cli/validators/core.py @@ -11,7 +11,7 @@ from multiversx_sdk_cli.cli_password import load_password from multiversx_sdk_cli.config import (GAS_PER_DATA_BYTE, MIN_GAS_LIMIT, MetaChainSystemSCsCost) -from multiversx_sdk_cli.contracts import SmartContract +from multiversx_sdk_cli.contracts import prepare_execute_transaction_data from multiversx_sdk_cli.errors import BadUsage from multiversx_sdk_cli.validators.validators_file import ValidatorsFile @@ -68,7 +68,7 @@ def prepare_transaction_data_for_stake(node_operator_address: Address, validator if reward_address: call_arguments.append(f"0x{reward_address.to_hex()}") - data = SmartContract().prepare_execute_transaction_data("stake", call_arguments) + data = prepare_execute_transaction_data("stake", call_arguments) gas_limit = estimate_system_sc_call(str(data), MetaChainSystemSCsCost.STAKE, num_of_nodes) return str(data), gas_limit diff --git a/multiversx_sdk_cli/validators/validators_file.py b/multiversx_sdk_cli/validators/validators_file.py index 765b4679..8da16bd5 100644 --- a/multiversx_sdk_cli/validators/validators_file.py +++ b/multiversx_sdk_cli/validators/validators_file.py @@ -1,5 +1,9 @@ import json from pathlib import Path +from typing import List + +from multiversx_sdk_wallet import ValidatorSigner +from multiversx_sdk_wallet.validator_pem import ValidatorPEM from multiversx_sdk_cli import guards from multiversx_sdk_cli.errors import CannotReadValidatorsData @@ -16,6 +20,20 @@ def get_num_of_nodes(self) -> int: def get_validators_list(self): return self._validators_data.get("validators", []) + def load_signers(self) -> List[ValidatorSigner]: + signers: List[ValidatorSigner] = [] + for validator in self.get_validators_list(): + # Get path of "pemFile", make it absolute + validator_pem = Path(validator.get("pemFile")).expanduser() + validator_pem = validator_pem if validator_pem.is_absolute() else self.validators_file_path.parent / validator_pem + + pem_file = ValidatorPEM.from_file(validator_pem) + + validator_signer = ValidatorSigner(pem_file.secret_key) + signers.append(validator_signer) + + return signers + def _read_json_file_validators(self): val_file = self.validators_file_path.expanduser() guards.is_file(val_file) diff --git a/pyproject.toml b/pyproject.toml index 933044e8..a4f9d6ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "multiversx-sdk-cli" -version = "9.0.2" +version = "9.1.0" authors = [ { name="MultiversX" }, ]