From 0780907f57c834eef12d75047c985bb49e35d4a9 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 17 Nov 2023 16:08:52 +0200 Subject: [PATCH 01/13] contract deploy using sc factory --- multiversx_sdk_cli/cli_contracts.py | 33 ++++++----- multiversx_sdk_cli/cli_output.py | 7 +-- multiversx_sdk_cli/constants.py | 2 + multiversx_sdk_cli/contracts.py | 87 +++++++++++++---------------- pyproject.toml | 6 +- requirements.txt | 6 +- 6 files changed, 69 insertions(+), 72 deletions(-) diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 5b34d106..44785d55 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -3,7 +3,9 @@ from pathlib import Path from typing import Any, List -from multiversx_sdk_core import Address, Transaction +from multiversx_sdk_core import (Address, AddressComputer, TokenComputer, + Transaction) +from multiversx_sdk_core.transaction_factories import TransactionsFactoryConfig from multiversx_sdk_network_providers.proxy_network_provider import \ ProxyNetworkProvider @@ -11,12 +13,14 @@ from multiversx_sdk_cli.accounts import Account, LedgerAccount 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 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 @@ -299,23 +303,22 @@ def deploy(args: Any): 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) - tx = contract.deploy(sender, arguments, gas_price, gas_limit, value, args.chain, version, args.guardian, args.options) + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config, TokenComputer()) + + address_computer = AddressComputer(NUMBER_OF_SHARDS) + contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=sender.nonce) + + tx = contract.get_deploy_transaction(sender, args) 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()) + 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) + _send_or_simulate(tx, contract_address, args) def _prepare_contract(args: Any) -> SmartContract: @@ -435,9 +438,9 @@ def query(args: Any): 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) 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/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..fbfff782 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -1,14 +1,17 @@ import base64 import logging +from pathlib import Path 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 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.constants import DEFAULT_HRP from multiversx_sdk_cli.utils import Object logger = logging.getLogger("contracts") @@ -61,39 +64,47 @@ 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() +class IConfig(Protocol): + chain_id: str + min_gas_limit: int + gas_limit_per_byte: int - 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 +class IToken(Protocol): + identifier: str + nonce: int - 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 - ) - if guardian: - tx.guardian = guardian +class ITokenComputer(Protocol): + def is_fungible(self, token: IToken) -> bool: + ... + + def extract_identifier_from_extended_identifier(self, identifier: str) -> str: + ... + +class SmartContract: + def __init__(self, config: IConfig, token_computer: ITokenComputer): + self._factory = SmartContractTransactionsFactory(config, token_computer) + + def get_deploy_transaction(self, owner: Account, args: Any) -> Transaction: + tx = self._factory.create_transaction_for_deploy( + sender=owner.address, + bytecode=Path(args.bytecode), + gas_limit=int(args.gas_limit), + arguments=args.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 = owner.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: @@ -294,21 +305,3 @@ def sum_flag_values(flag_value_pairs: List[Tuple[int, bool]]) -> int: 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/pyproject.toml b/pyproject.toml index 979d699f..7ea4ae28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,9 +27,9 @@ dependencies = [ "semver", "requests-cache", "rich==13.3.4", - "multiversx-sdk-network-providers<0.13.0", - "multiversx-sdk-wallet<0.9.0,", - "multiversx-sdk-core<0.8.0" + "multiversx-sdk-network-providers>=0.12.0,<0.13.0", + "multiversx-sdk-wallet>=0.8.0,<0.9.0,", + "multiversx-sdk-core>=0.7.0,<0.8.0" ] [tool.hatch.build] diff --git a/requirements.txt b/requirements.txt index ea54e91b..028aa23d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,6 @@ semver requests-cache rich==13.3.4 -multiversx-sdk-core<0.8.0 -multiversx-sdk-network-providers<0.13.0 -multiversx-sdk-wallet<0.9.0 +multiversx-sdk-core>=0.7.0,<0.8.0 +multiversx-sdk-network-providers>=0.12.0,<0.13.0 +multiversx-sdk-wallet>=0.8.0,<0.9.0 From 225effdf3399efb98226514606cee17ca869258f Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 17 Nov 2023 16:43:16 +0200 Subject: [PATCH 02/13] sc call using sc factory --- multiversx_sdk_cli/cli_contracts.py | 17 ++++------ multiversx_sdk_cli/contracts.py | 49 ++++++++++------------------- 2 files changed, 23 insertions(+), 43 deletions(-) diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 44785d55..effb8321 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -386,22 +386,17 @@ def call(args: Any): 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) - tx = contract.execute(sender, function, arguments, gas_price, gas_limit, value, args.chain, version, args.guardian, args.options) + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config, TokenComputer()) + contract_address = Address.new_from_bech32(args.contract) + + tx = contract.get_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): diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index fbfff782..16ec48ff 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -9,7 +9,7 @@ SmartContractTransactionsFactory from multiversx_sdk_network_providers.interface import IAddress, IContractQuery -from multiversx_sdk_cli import config, constants, errors +from multiversx_sdk_cli import config, errors from multiversx_sdk_cli.accounts import Account, EmptyAddress from multiversx_sdk_cli.constants import DEFAULT_HRP from multiversx_sdk_cli.utils import Object @@ -107,40 +107,25 @@ def get_deploy_transaction(self, owner: Account, args: Any) -> Transaction: 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()}" + def get_execute_transaction(self, owner: Account, args: Any) -> Transaction: + contract_address = Address.new_from_bech32(args.contract) + arguments = args.arguments or [] - for arg in arguments: - tx_data += f"@{_prepare_argument(arg)}" - - return TransactionPayload.from_str(tx_data) - - 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 - - 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 + tx = self._factory.create_transaction_for_execute( + sender=owner.address, + contract=contract_address, + function=args.function, + gas_limit=int(args.gas_limit), + arguments=arguments, + native_transfer_amount=int(args.value), + token_transfers=[] ) + tx.nonce = owner.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: From f5b68ddfdc7d3b735691d329a053ce11bd88e574 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 21 Nov 2023 17:05:45 +0200 Subject: [PATCH 03/13] use smart contract factory & add unit tests --- multiversx_sdk_cli/cli_contracts.py | 46 ++--- multiversx_sdk_cli/cli_shared.py | 6 +- multiversx_sdk_cli/contracts.py | 192 ++++++++++-------- multiversx_sdk_cli/dns.py | 18 +- multiversx_sdk_cli/localnet/genesis.py | 13 +- .../tests/test_cli_contracts.py | 88 ++++++++ .../tests/test_code_metadata.py | 21 -- multiversx_sdk_cli/tests/test_contracts.py | 23 +-- multiversx_sdk_cli/tests/testdata/adder.wasm | Bin 837 -> 697 bytes multiversx_sdk_cli/validators/core.py | 4 +- 10 files changed, 232 insertions(+), 179 deletions(-) delete mode 100644 multiversx_sdk_cli/tests/test_code_metadata.py mode change 100755 => 100644 multiversx_sdk_cli/tests/testdata/adder.wasm diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index effb8321..9c5ff67d 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -16,7 +16,7 @@ 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 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 @@ -321,15 +321,6 @@ def deploy(args: Any): _send_or_simulate(tx, contract_address, 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: @@ -386,8 +377,8 @@ def call(args: Any): cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - sender = _prepare_sender(args) cli_shared.prepare_chain_id_in_args(args) + sender = _prepare_sender(args) config = TransactionsFactoryConfig(args.chain) contract = SmartContract(config, TokenComputer()) @@ -401,35 +392,36 @@ def call(args: Any): 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) + sender = _prepare_sender(args) - tx = contract.upgrade(sender, arguments, gas_price, gas_limit, value, args.chain, version, args.guardian, args.options) + config = TransactionsFactoryConfig(args.chain) + contract = SmartContract(config, TokenComputer()) + contract_address = Address.new_from_bech32(args.contract) + + tx = contract.get_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) diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index 1a7f970f..46c00519 100644 --- a/multiversx_sdk_cli/cli_shared.py +++ b/multiversx_sdk_cli/cli_shared.py @@ -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): diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 16ec48ff..efd8e2f3 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -1,7 +1,7 @@ import base64 import logging from pathlib import Path -from typing import Any, List, Optional, Protocol, Sequence, Tuple +from typing import Any, List, Optional, Protocol, Sequence from multiversx_sdk_core import Transaction, TransactionPayload from multiversx_sdk_core.address import Address @@ -9,8 +9,8 @@ SmartContractTransactionsFactory from multiversx_sdk_network_providers.interface import IAddress, IContractQuery -from multiversx_sdk_cli import config, errors -from multiversx_sdk_cli.accounts import Account, EmptyAddress +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 @@ -88,11 +88,14 @@ def __init__(self, config: IConfig, token_computer: ITokenComputer): self._factory = SmartContractTransactionsFactory(config, token_computer) def get_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=args.arguments, + arguments=arguments, native_transfer_amount=int(args.value), is_upgradeable=args.metadata_upgradeable, is_readable=args.metadata_readable, @@ -107,12 +110,13 @@ def get_deploy_transaction(self, owner: Account, args: Any) -> Transaction: return tx - def get_execute_transaction(self, owner: Account, args: Any) -> Transaction: + def get_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) tx = self._factory.create_transaction_for_execute( - sender=owner.address, + sender=caller.address, contract=contract_address, function=args.function, gas_limit=int(args.gas_limit), @@ -120,98 +124,82 @@ def get_execute_transaction(self, owner: Account, args: Any) -> Transaction: native_transfer_amount=int(args.value), token_transfers=[] ) - tx.nonce = owner.nonce + tx.nonce = caller.nonce tx.version = int(args.version) tx.options = int(args.options) tx.guardian = args.guardian - tx.signature = bytes.fromhex(owner.sign_transaction(tx)) + 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 - ) - - if guardian: - tx.guardian = guardian + def get_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 = owner.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_upgrade_transaction_data(self, arguments: List[Any]) -> TransactionPayload: - tx_data = f"upgradeContract@{self.bytecode}@{self.metadata.to_hex()}" + return tx - 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[Address] = 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[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] - query = ContractQuery(self.address, function, value, prepared_arguments, caller) + query = ContractQuery(contract_address, function, value, prepared_arguments, 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]: @@ -228,6 +216,42 @@ 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) + + def _prepare_argument(argument: Any): as_str = str(argument) as_hex = _to_hex(as_str) @@ -282,11 +306,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 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..f8e27c6e 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,90 @@ def test_contract_call(): ] ) assert Path.is_file(output_file) == True + + +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 _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(" ", "") + print(out) + return json.loads(out)[0] 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..a5d2ecf6 100644 --- a/multiversx_sdk_cli/tests/test_contracts.py +++ b/multiversx_sdk_cli/tests/test_contracts.py @@ -3,14 +3,12 @@ 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, +from multiversx_sdk_cli.contracts import (_interpret_as_number_if_safely, _prepare_argument) logging.basicConfig(level=logging.INFO) @@ -23,23 +21,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' 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 820c0f40e7ae25e746ffd9f676bc75462bb34a46..ffb39b0d4e26f071164fbc6fac3f4800246f37ac GIT binary patch literal 697 zcmZuvO>dh(5S`g2n6M3OVyl&-9w?{wlv~fmQe9Q5qDX1aur_OK*+8^FE1&RB$Td<9 z{XP9t?k?a+m3m-z=e;*?J_Lv=M*zSNP6Chswh3w5{)7ZuNNC%|<}v`b@3A@HxtV(Oh(N^rbH^?(v9O()FPHeN`b}kT8f8wAu37H{e!ND z*hau#g}@;IHZ|e%!7<-^0ugRQ_(%*h0geI8txrCS4q7nf1lViMbh-3;cv$+uCfY^A zBtU+qhA`RGOHM942Vdk36r6Q6m&9o^Ce=iKAxEJWIBpw}ShA>m%A u>uPg1AYBFpWb8(_s~cl)f2p#_f}+V6QU&EI5W`PZReDusFlId;kH^3K_p3et literal 837 zcmaJ<$!^m?82~|*b7I5BasRTRY-xeGl?foXlz$rKsk+}>}wyO z2OfnN;F-cKP5}wwgXf=b9ghs6NDu(<&eoVJrBIEDQZNQL=r_O^HDE|Hp(1C5QJFs9 zP6pL%Ci9Uj2dh#R00!+(7V*QhJe1|rv{=NcjDkF_5}B5O))mY0s7f|q0mPupNX7C& zo@cq!?QGJ^EElnSl!*wakzQ|zC)vO@ZE76bZ`fg;CI8}g|BfB$bm^Q|rgJ$xKFS`8 zl|60Cli2VXqg4ktjtd(}*Gq!?_eE;Y1XY zlHO0!yM?Iu=en-J2h&zVK)K%5aKi&;*Z>09C+C(@&wRM+X3bu+?8ePs8kdGWAouwz zw>i8F+blS><~~0)Ah3Py^Puq|f9=}vBIu0+bkgs`5fAy>2KNYmXGfdF-iMm<;<+iheigC%+mauwS2IWdq~y)vV3%MpLj&)ezf_h&*B;gFm^!f9Rj;q gRLP4xOJgssl8MZ{Z03pPXD%SnmkF$OzFx0?0TE^JrT_o{ 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 From b030c136fd67915b9957d3805cc80cf793a3f7df Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 22 Nov 2023 12:07:46 +0200 Subject: [PATCH 04/13] move .yml file --- .github/{workflows => }/release.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => }/release.yml (100%) 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 From f55036dc09b69a239ef360637fe4c08d1a76a7f7 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 24 Nov 2023 15:06:28 +0200 Subject: [PATCH 05/13] integrated the delegation factory --- multiversx_sdk_cli/cli_contracts.py | 57 +- multiversx_sdk_cli/cli_delegation.py | 112 ++-- multiversx_sdk_cli/cli_transactions.py | 2 +- multiversx_sdk_cli/contracts.py | 6 +- multiversx_sdk_cli/delegation/__init__.py | 24 +- .../delegation/staking_provider.py | 496 ++++++++++++------ .../tests/test_cli_contracts.py | 17 +- .../tests/test_cli_staking_provider.py | 288 ++++++++++ 8 files changed, 730 insertions(+), 272 deletions(-) create mode 100644 multiversx_sdk_cli/tests/test_cli_staking_provider.py diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 9c5ff67d..37570626 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -9,10 +9,8 @@ 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 @@ -302,15 +300,15 @@ def deploy(args: Any): logger.debug("deploy") cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - - 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, TokenComputer()) address_computer = AddressComputer(NUMBER_OF_SHARDS) - contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=sender.nonce) + contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce) tx = contract.get_deploy_transaction(sender, args) tx = _sign_guarded_tx(args, tx) @@ -321,43 +319,6 @@ def deploy(args: Any): _send_or_simulate(tx, contract_address, args) -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.nonce = args.nonce - if args.recall_nonce: - sender.sync_nonce(ProxyNetworkProvider(args.proxy)) - - return sender - - -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() - - return sender - - def _sign_guarded_tx(args: Any, tx: Transaction) -> Transaction: try: guardian_account = cli_shared.prepare_guardian_account(args) @@ -376,10 +337,10 @@ 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) - sender = _prepare_sender(args) + cli_shared.prepare_nonce_in_args(args) + sender = cli_shared.prepare_account(args) config = TransactionsFactoryConfig(args.chain) contract = SmartContract(config, TokenComputer()) contract_address = Address.new_from_bech32(args.contract) @@ -394,10 +355,10 @@ def upgrade(args: Any): logger.debug("upgrade") cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) - cli_shared.prepare_chain_id_in_args(args) - sender = _prepare_sender(args) + cli_shared.prepare_nonce_in_args(args) + sender = cli_shared.prepare_account(args) config = TransactionsFactoryConfig(args.chain) contract = SmartContract(config, TokenComputer()) contract_address = Address.new_from_bech32(args.contract) @@ -437,7 +398,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..128c59f7 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.get_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.get_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.get_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.get_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.get_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.get_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.get_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.get_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.get_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.get_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.get_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.get_transaction_for_setting_metadata(sender, args) cli_shared.send_or_simulate(tx, args) 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/contracts.py b/multiversx_sdk_cli/contracts.py index efd8e2f3..d16d36b6 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -102,7 +102,7 @@ def get_deploy_transaction(self, owner: Account, args: Any) -> Transaction: is_payable=args.metadata_payable, is_payable_by_sc=args.metadata_payable_by_sc ) - tx.nonce = owner.nonce + tx.nonce = int(args.nonce) tx.version = int(args.version) tx.options = int(args.options) tx.guardian = args.guardian @@ -124,7 +124,7 @@ def get_execute_transaction(self, caller: Account, args: Any) -> Transaction: native_transfer_amount=int(args.value), token_transfers=[] ) - tx.nonce = caller.nonce + tx.nonce = int(args.nonce) tx.version = int(args.version) tx.options = int(args.options) tx.guardian = args.guardian @@ -149,7 +149,7 @@ def get_upgrade_transaction(self, owner: Account, args: Any): is_payable=args.metadata_payable, is_payable_by_sc=args.metadata_payable_by_sc ) - tx.nonce = owner.nonce + tx.nonce = int(args.nonce) tx.version = int(args.version) tx.options = int(args.options) tx.guardian = args.guardian 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..00971ec2 100644 --- a/multiversx_sdk_cli/delegation/staking_provider.py +++ b/multiversx_sdk_cli/delegation/staking_provider.py @@ -1,169 +1,347 @@ -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_core import Address, Transaction +from multiversx_sdk_core.transaction_factories import \ + DelegationTransactionsFactory +from multiversx_sdk_wallet import ValidatorPublicKey from multiversx_sdk_wallet.validator_pem import ValidatorPEM from multiversx_sdk_wallet.validator_signer import ValidatorSigner -from multiversx_sdk_cli import utils from multiversx_sdk_cli.accounts import Account 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.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 get_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 get_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 get_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 get_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 get_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 get_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 get_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 get_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 get_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 get_transaction_for_automatic_activation(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + + tx = self.__get_empty_transaction() + 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 + ) + + 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 get_transaction_for_redelegate_cap(self, owner: IAccount, args: Any) -> ITransaction: + delegation_contract = Address.new_from_bech32(args.delegation_contract) + + tx = self.__get_empty_transaction() + 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 + ) + + 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 get_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) + + pubkey = self._get_pubkey_for_signing(args) + + public_keys: List[ValidatorPublicKey] = [] + signed_messages: List[bytes] = [] + 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(pubkey) + + public_keys.append(pem_file.secret_key.generate_public_key()) + signed_messages.append(signed_message) + + return public_keys, signed_messages + + def _get_pubkey_for_signing(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) + + return account.address.get_public_key() + + def __get_empty_transaction(self) -> Transaction: + # I've done this to get rid of the warning "tx possibly unbound" + # the if-elif statement should always execute because the args are required for the argparser + return Transaction( + sender="", + receiver="", + gas_limit=0, + chain_id="" + ) diff --git a/multiversx_sdk_cli/tests/test_cli_contracts.py b/multiversx_sdk_cli/tests/test_cli_contracts.py index f8e27c6e..90901f98 100644 --- a/multiversx_sdk_cli/tests/test_cli_contracts.py +++ b/multiversx_sdk_cli/tests/test_cli_contracts.py @@ -239,6 +239,22 @@ def test_contract_flow(capsys: Any): ]) +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 True if return_code else False + + def _read_stdout(capsys: Any) -> str: return capsys.readouterr().out.strip() @@ -251,5 +267,4 @@ def get_contract_address(capsys: Any): def get_query_response(capsys: Any): out = _read_stdout(capsys).replace("\n", "").replace(" ", "") - print(out) return json.loads(out)[0] 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) From 80646608b79468d192c664966fc7358ec4848feb Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 24 Nov 2023 17:14:49 +0200 Subject: [PATCH 06/13] first part of fixes after review --- multiversx_sdk_cli/contracts.py | 4 ++-- multiversx_sdk_cli/tests/test_contracts.py | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index efd8e2f3..13465f28 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -164,7 +164,7 @@ def query_contract( function: str, arguments: List[Any], value: int = 0, - caller: Optional[Address] = None + caller: Optional[IAddress] = None ) -> List[Any]: response_data = query_detailed(contract_address, proxy, function, arguments, value, caller) return_data = response_data.return_data @@ -172,7 +172,7 @@ def query_contract( def query_detailed(contract_address: IAddress, proxy: INetworkProvider, function: str, arguments: List[Any], - value: int = 0, caller: Optional[Address] = None) -> Any: + value: int = 0, caller: Optional[IAddress] = 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] diff --git a/multiversx_sdk_cli/tests/test_contracts.py b/multiversx_sdk_cli/tests/test_contracts.py index a5d2ecf6..733969a3 100644 --- a/multiversx_sdk_cli/tests/test_contracts.py +++ b/multiversx_sdk_cli/tests/test_contracts.py @@ -9,7 +9,8 @@ from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.contract_verification import _create_request_signature from multiversx_sdk_cli.contracts import (_interpret_as_number_if_safely, - _prepare_argument) + _prepare_argument, + prepare_args_for_factory) logging.basicConfig(level=logging.INFO) @@ -66,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" From 5cfe31aba4a2fd1d549a93d5f99f01fd5d6103df Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 27 Nov 2023 12:08:30 +0200 Subject: [PATCH 07/13] fixes after review --- multiversx_sdk_cli/cli_contracts.py | 9 ++++----- multiversx_sdk_cli/contracts.py | 25 +++++++------------------ 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 9c5ff67d..5d6d1121 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -3,8 +3,7 @@ from pathlib import Path from typing import Any, List -from multiversx_sdk_core import (Address, AddressComputer, TokenComputer, - 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 @@ -307,7 +306,7 @@ def deploy(args: Any): cli_shared.prepare_chain_id_in_args(args) config = TransactionsFactoryConfig(args.chain) - contract = SmartContract(config, TokenComputer()) + contract = SmartContract(config) address_computer = AddressComputer(NUMBER_OF_SHARDS) contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=sender.nonce) @@ -381,7 +380,7 @@ def call(args: Any): sender = _prepare_sender(args) config = TransactionsFactoryConfig(args.chain) - contract = SmartContract(config, TokenComputer()) + contract = SmartContract(config) contract_address = Address.new_from_bech32(args.contract) tx = contract.get_execute_transaction(sender, args) @@ -399,7 +398,7 @@ def upgrade(args: Any): sender = _prepare_sender(args) config = TransactionsFactoryConfig(args.chain) - contract = SmartContract(config, TokenComputer()) + contract = SmartContract(config) contract_address = Address.new_from_bech32(args.contract) tx = contract.get_upgrade_transaction(sender, args) diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 13465f28..634cbdcb 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import Any, List, Optional, Protocol, Sequence -from multiversx_sdk_core import Transaction, TransactionPayload +from multiversx_sdk_core import TokenComputer, Transaction, TransactionPayload from multiversx_sdk_core.address import Address from multiversx_sdk_core.transaction_factories import \ SmartContractTransactionsFactory @@ -70,22 +70,9 @@ class IConfig(Protocol): gas_limit_per_byte: int -class IToken(Protocol): - identifier: str - nonce: int - - -class ITokenComputer(Protocol): - def is_fungible(self, token: IToken) -> bool: - ... - - def extract_identifier_from_extended_identifier(self, identifier: str) -> str: - ... - - class SmartContract: - def __init__(self, config: IConfig, token_computer: ITokenComputer): - self._factory = SmartContractTransactionsFactory(config, token_computer) + def __init__(self, config: IConfig): + self._factory = SmartContractTransactionsFactory(config, TokenComputer()) def get_deploy_transaction(self, owner: Account, args: Any) -> Transaction: arguments = args.arguments or [] @@ -175,9 +162,10 @@ def query_detailed(contract_address: IAddress, proxy: INetworkProvider, function value: int = 0, caller: Optional[IAddress] = 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] + arguments_hex = [_prepare_argument(arg) for arg in arguments] + prepared_arguments_bytes = [bytes.fromhex(arg) for arg in arguments_hex] - query = ContractQuery(contract_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. @@ -252,6 +240,7 @@ def hex_to_bytes(arg: str): 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) From 34f318554457580ebcc42947c76c703f2614b1a0 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 29 Nov 2023 10:26:40 +0200 Subject: [PATCH 08/13] fix test --- multiversx_sdk_cli/tests/test_cli_validators.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From d3e3af6af67bea489d4b6980bb347ad9d5c1a218 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 29 Nov 2023 17:43:12 +0200 Subject: [PATCH 09/13] implement transfer and execute --- multiversx_sdk_cli/cli_contracts.py | 7 +++++++ multiversx_sdk_cli/contracts.py | 26 +++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index d95ea55f..c57d43a6 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -96,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" @@ -234,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)") diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 29b04abd..54a9dfc4 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -3,7 +3,8 @@ from pathlib import Path from typing import Any, List, Optional, Protocol, Sequence -from multiversx_sdk_core import TokenComputer, Transaction, TransactionPayload +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 @@ -102,14 +103,18 @@ def get_execute_transaction(self, caller: Account, args: Any) -> Transaction: 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) + 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=int(args.value), - token_transfers=[] + native_transfer_amount=value, + token_transfers=token_transfers ) tx.nonce = int(args.nonce) tx.version = int(args.version) @@ -144,6 +149,21 @@ def get_upgrade_transaction(self, owner: Account, args: Any): return tx + def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: + token_computer = TokenComputer() + token_transfers: List[TokenTransfer] = [] + + for i in range(len(transfers) - 1): + identifier = transfers[i] + amount = int(transfers[i + 1]) + nonce = token_computer.extract_nonce_from_extended_identifier(identifier) + + token = Token(identifier, nonce) + transfer = TokenTransfer(token, amount) + token_transfers.append(transfer) + + return token_transfers + def query_contract( contract_address: IAddress, From f70056d01c885352d688c9485ba8736a634a0744 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 4 Dec 2023 11:54:31 +0200 Subject: [PATCH 10/13] fixes and tests --- multiversx_sdk_cli/contracts.py | 4 +- .../tests/test_cli_contracts.py | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 54a9dfc4..a82dae40 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -105,7 +105,7 @@ def get_execute_transaction(self, caller: Account, args: Any) -> Transaction: value = int(args.value) transfers = args.token_transfers - token_transfers = self._prepare_token_transfers(transfers) + token_transfers = self._prepare_token_transfers(transfers) if transfers else [] tx = self._factory.create_transaction_for_execute( sender=caller.address, @@ -153,7 +153,7 @@ def _prepare_token_transfers(self, transfers: List[str]) -> List[TokenTransfer]: token_computer = TokenComputer() token_transfers: List[TokenTransfer] = [] - for i in range(len(transfers) - 1): + 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) diff --git a/multiversx_sdk_cli/tests/test_cli_contracts.py b/multiversx_sdk_cli/tests/test_cli_contracts.py index 90901f98..567f590e 100644 --- a/multiversx_sdk_cli/tests/test_cli_contracts.py +++ b/multiversx_sdk_cli/tests/test_cli_contracts.py @@ -168,6 +168,43 @@ 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" @@ -268,3 +305,9 @@ def get_contract_address(capsys: Any): 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"] From 836cb094aedfa026880910d22da893eadabd46ff Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 4 Dec 2023 13:25:56 +0200 Subject: [PATCH 11/13] fixes after review --- multiversx_sdk_cli/cli_contracts.py | 6 +- multiversx_sdk_cli/cli_delegation.py | 24 +++---- multiversx_sdk_cli/cli_shared.py | 5 +- multiversx_sdk_cli/contracts.py | 6 +- .../delegation/staking_provider.py | 63 +++++++------------ .../tests/test_cli_contracts.py | 2 +- .../validators/validators_file.py | 18 ++++++ 7 files changed, 63 insertions(+), 61 deletions(-) diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index d95ea55f..48601c74 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -309,7 +309,7 @@ def deploy(args: Any): address_computer = AddressComputer(NUMBER_OF_SHARDS) contract_address = address_computer.compute_contract_address(deployer=sender.address, deployment_nonce=args.nonce) - tx = contract.get_deploy_transaction(sender, args) + tx = contract.prepare_deploy_transaction(sender, args) tx = _sign_guarded_tx(args, tx) logger.info("Contract address: %s", contract_address.to_bech32()) @@ -344,7 +344,7 @@ def call(args: Any): contract = SmartContract(config) contract_address = Address.new_from_bech32(args.contract) - tx = contract.get_execute_transaction(sender, args) + tx = contract.prepare_execute_transaction(sender, args) tx = _sign_guarded_tx(args, tx) _send_or_simulate(tx, contract_address, args) @@ -362,7 +362,7 @@ def upgrade(args: Any): contract = SmartContract(config) contract_address = Address.new_from_bech32(args.contract) - tx = contract.get_upgrade_transaction(sender, args) + tx = contract.prepare_upgrade_transaction(sender, args) tx = _sign_guarded_tx(args, tx) _send_or_simulate(tx, contract_address, args) diff --git a/multiversx_sdk_cli/cli_delegation.py b/multiversx_sdk_cli/cli_delegation.py index 128c59f7..3d76b348 100644 --- a/multiversx_sdk_cli/cli_delegation.py +++ b/multiversx_sdk_cli/cli_delegation.py @@ -149,7 +149,7 @@ def do_create_delegation_contract(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_new_delegation_contract(sender, args) + tx = delegation.prepare_transaction_for_new_delegation_contract(sender, args) cli_shared.send_or_simulate(tx, args) @@ -177,7 +177,7 @@ def add_new_nodes(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_adding_nodes(sender, args) + tx = delegation.prepare_transaction_for_adding_nodes(sender, args) cli_shared.send_or_simulate(tx, args) @@ -191,7 +191,7 @@ def remove_nodes(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_removing_nodes(sender, args) + tx = delegation.prepare_transaction_for_removing_nodes(sender, args) cli_shared.send_or_simulate(tx, args) @@ -205,7 +205,7 @@ def stake_nodes(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_staking_nodes(sender, args) + tx = delegation.prepare_transaction_for_staking_nodes(sender, args) cli_shared.send_or_simulate(tx, args) @@ -219,7 +219,7 @@ def unbond_nodes(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_unbonding_nodes(sender, args) + tx = delegation.prepare_transaction_for_unbonding_nodes(sender, args) cli_shared.send_or_simulate(tx, args) @@ -233,7 +233,7 @@ def unstake_nodes(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_unstaking_nodes(sender, args) + tx = delegation.prepare_transaction_for_unstaking_nodes(sender, args) cli_shared.send_or_simulate(tx, args) @@ -247,7 +247,7 @@ def unjail_nodes(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_unjailing_nodes(sender, args) + tx = delegation.prepare_transaction_for_unjailing_nodes(sender, args) cli_shared.send_or_simulate(tx, args) @@ -261,7 +261,7 @@ def change_service_fee(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_changing_service_fee(sender, args) + tx = delegation.prepare_transaction_for_changing_service_fee(sender, args) cli_shared.send_or_simulate(tx, args) @@ -275,7 +275,7 @@ def modify_delegation_cap(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_modifying_delegation_cap(sender, args) + tx = delegation.prepare_transaction_for_modifying_delegation_cap(sender, args) cli_shared.send_or_simulate(tx, args) @@ -289,7 +289,7 @@ def automatic_activation(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_automatic_activation(sender, args) + tx = delegation.prepare_transaction_for_automatic_activation(sender, args) cli_shared.send_or_simulate(tx, args) @@ -303,7 +303,7 @@ def redelegate_cap(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_redelegate_cap(sender, args) + tx = delegation.prepare_transaction_for_redelegate_cap(sender, args) cli_shared.send_or_simulate(tx, args) @@ -317,5 +317,5 @@ def set_metadata(args: Any): config = TransactionsFactoryConfig(args.chain) delegation = DelegationOperations(config) - tx = delegation.get_transaction_for_setting_metadata(sender, args) + tx = delegation.prepare_transaction_for_setting_metadata(sender, args) cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index 46c00519..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) @@ -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/contracts.py b/multiversx_sdk_cli/contracts.py index 29b04abd..314ed96d 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -74,7 +74,7 @@ class SmartContract: def __init__(self, config: IConfig): self._factory = SmartContractTransactionsFactory(config, TokenComputer()) - def get_deploy_transaction(self, owner: Account, args: Any) -> Transaction: + def prepare_deploy_transaction(self, owner: Account, args: Any) -> Transaction: arguments = args.arguments or [] arguments = prepare_args_for_factory(arguments) @@ -97,7 +97,7 @@ def get_deploy_transaction(self, owner: Account, args: Any) -> Transaction: return tx - def get_execute_transaction(self, caller: Account, args: Any) -> Transaction: + 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) @@ -119,7 +119,7 @@ def get_execute_transaction(self, caller: Account, args: Any) -> Transaction: return tx - def get_upgrade_transaction(self, owner: Account, args: Any): + 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) diff --git a/multiversx_sdk_cli/delegation/staking_provider.py b/multiversx_sdk_cli/delegation/staking_provider.py index 00971ec2..3d0b4596 100644 --- a/multiversx_sdk_cli/delegation/staking_provider.py +++ b/multiversx_sdk_cli/delegation/staking_provider.py @@ -1,15 +1,14 @@ from pathlib import Path from typing import Any, List, Protocol, Tuple -from multiversx_sdk_core import Address, Transaction +from multiversx_sdk_core import Address from multiversx_sdk_core.transaction_factories import \ DelegationTransactionsFactory from multiversx_sdk_wallet import ValidatorPublicKey -from multiversx_sdk_wallet.validator_pem import ValidatorPEM -from multiversx_sdk_wallet.validator_signer import ValidatorSigner from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.cli_password import load_password +from multiversx_sdk_cli.errors import BadUsage from multiversx_sdk_cli.interfaces import IAddress, ITransaction from multiversx_sdk_cli.validators.validators_file import ValidatorsFile @@ -42,7 +41,7 @@ class DelegationOperations: def __init__(self, config: IConfig) -> None: self._factory = DelegationTransactionsFactory(config) - def get_transaction_for_new_delegation_contract(self, owner: IAccount, args: Any) -> ITransaction: + 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), @@ -60,7 +59,7 @@ def get_transaction_for_new_delegation_contract(self, owner: IAccount, args: Any return tx - def get_transaction_for_adding_nodes(self, owner: IAccount, args: Any) -> ITransaction: + 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) @@ -81,7 +80,7 @@ def get_transaction_for_adding_nodes(self, owner: IAccount, args: Any) -> ITrans return tx - def get_transaction_for_removing_nodes(self, owner: IAccount, args: Any) -> ITransaction: + 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) @@ -101,7 +100,7 @@ def get_transaction_for_removing_nodes(self, owner: IAccount, args: Any) -> ITra return tx - def get_transaction_for_staking_nodes(self, owner: IAccount, args: Any) -> ITransaction: + 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) @@ -121,7 +120,7 @@ def get_transaction_for_staking_nodes(self, owner: IAccount, args: Any) -> ITran return tx - def get_transaction_for_unbonding_nodes(self, owner: IAccount, args: Any) -> ITransaction: + 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) @@ -141,7 +140,7 @@ def get_transaction_for_unbonding_nodes(self, owner: IAccount, args: Any) -> ITr return tx - def get_transaction_for_unstaking_nodes(self, owner: IAccount, args: Any) -> ITransaction: + 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) @@ -161,7 +160,7 @@ def get_transaction_for_unstaking_nodes(self, owner: IAccount, args: Any) -> ITr return tx - def get_transaction_for_unjailing_nodes(self, owner: IAccount, args: Any) -> ITransaction: + 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) @@ -181,7 +180,7 @@ def get_transaction_for_unjailing_nodes(self, owner: IAccount, args: Any) -> ITr return tx - def get_transaction_for_changing_service_fee(self, owner: IAccount, args: Any) -> ITransaction: + 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( @@ -200,7 +199,7 @@ def get_transaction_for_changing_service_fee(self, owner: IAccount, args: Any) - return tx - def get_transaction_for_modifying_delegation_cap(self, owner: IAccount, args: Any) -> ITransaction: + 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( @@ -219,10 +218,9 @@ def get_transaction_for_modifying_delegation_cap(self, owner: IAccount, args: An return tx - def get_transaction_for_automatic_activation(self, owner: IAccount, args: Any) -> ITransaction: + def prepare_transaction_for_automatic_activation(self, owner: IAccount, args: Any) -> ITransaction: delegation_contract = Address.new_from_bech32(args.delegation_contract) - tx = self.__get_empty_transaction() if args.set: tx = self._factory.create_transaction_for_setting_automatic_activation( sender=owner.address, @@ -233,6 +231,8 @@ def get_transaction_for_automatic_activation(self, owner: IAccount, args: Any) - 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) @@ -245,10 +245,9 @@ def get_transaction_for_automatic_activation(self, owner: IAccount, args: Any) - return tx - def get_transaction_for_redelegate_cap(self, owner: IAccount, args: Any) -> ITransaction: + def prepare_transaction_for_redelegate_cap(self, owner: IAccount, args: Any) -> ITransaction: delegation_contract = Address.new_from_bech32(args.delegation_contract) - tx = self.__get_empty_transaction() if args.set: tx = self._factory.create_transaction_for_setting_cap_check_on_redelegate_rewards( sender=owner.address, @@ -259,6 +258,8 @@ def get_transaction_for_redelegate_cap(self, owner: IAccount, args: Any) -> ITra 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) @@ -271,7 +272,7 @@ def get_transaction_for_redelegate_cap(self, owner: IAccount, args: Any) -> ITra return tx - def get_transaction_for_setting_metadata(self, owner: IAccount, args: Any) -> ITransaction: + 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( @@ -304,27 +305,21 @@ def _parse_public_bls_keys(self, public_bls_keys: str) -> List[ValidatorPublicKe 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_for_signing(args) + pubkey = self._get_pubkey_to_be_signed(args) public_keys: List[ValidatorPublicKey] = [] signed_messages: List[bytes] = [] - 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 + for signer in signers: + signed_message = signer.sign(pubkey) - pem_file = ValidatorPEM.from_file(validator_pem) - - validator_signer = ValidatorSigner(pem_file.secret_key) - signed_message = validator_signer.sign(pubkey) - - public_keys.append(pem_file.secret_key.generate_public_key()) + public_keys.append(signer.secret_key.generate_public_key()) signed_messages.append(signed_message) return public_keys, signed_messages - def _get_pubkey_for_signing(self, args: Any) -> bytes: + 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)) @@ -335,13 +330,3 @@ def _get_pubkey_for_signing(self, args: Any) -> bytes: account = Account(key_file=args.keyfile, password=password) return account.address.get_public_key() - - def __get_empty_transaction(self) -> Transaction: - # I've done this to get rid of the warning "tx possibly unbound" - # the if-elif statement should always execute because the args are required for the argparser - return Transaction( - sender="", - receiver="", - gas_limit=0, - chain_id="" - ) diff --git a/multiversx_sdk_cli/tests/test_cli_contracts.py b/multiversx_sdk_cli/tests/test_cli_contracts.py index 90901f98..5cb41bb3 100644 --- a/multiversx_sdk_cli/tests/test_cli_contracts.py +++ b/multiversx_sdk_cli/tests/test_cli_contracts.py @@ -252,7 +252,7 @@ def test_contract_deploy_without_required_arguments(): "--arguments", "0", "--send", "--wait-result" ]) - assert True if return_code else False + assert return_code def _read_stdout(capsys: Any) -> str: 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) From 884a7d0183013a16b7f9988002cdb4510aebf738 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Tue, 5 Dec 2023 11:05:43 +0200 Subject: [PATCH 12/13] fix adding nodes using ledger device --- multiversx_sdk_cli/delegation/staking_provider.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/multiversx_sdk_cli/delegation/staking_provider.py b/multiversx_sdk_cli/delegation/staking_provider.py index 3d0b4596..cbcb6160 100644 --- a/multiversx_sdk_cli/delegation/staking_provider.py +++ b/multiversx_sdk_cli/delegation/staking_provider.py @@ -6,7 +6,7 @@ DelegationTransactionsFactory from multiversx_sdk_wallet import ValidatorPublicKey -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.errors import BadUsage from multiversx_sdk_cli.interfaces import IAddress, ITransaction @@ -328,5 +328,7 @@ def _get_pubkey_to_be_signed(self, args: Any) -> bytes: 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() From e29fe6e7896e636dd6aaa430a15eb27e0e5287d9 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 11 Dec 2023 13:59:30 +0200 Subject: [PATCH 13/13] bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" }, ]