diff --git a/multiversx_sdk/abi/abi_definition.py b/multiversx_sdk/abi/abi_definition.py index 303585d3..6826f1e4 100644 --- a/multiversx_sdk/abi/abi_definition.py +++ b/multiversx_sdk/abi/abi_definition.py @@ -260,8 +260,6 @@ def __init__(self, name: str) -> None: @classmethod def from_dict(cls, data: Dict[str, Any]) -> "ExplicitEnumVariantDefinition": - fields = [FieldDefinition.from_dict(item) for item in data.get("fields", [])] - return cls( name=data.get("name", "") ) diff --git a/multiversx_sdk/account_management/account_transactions_factory.py b/multiversx_sdk/account_management/account_transactions_factory.py index d9d5c624..63dfd2a6 100644 --- a/multiversx_sdk/account_management/account_transactions_factory.py +++ b/multiversx_sdk/account_management/account_transactions_factory.py @@ -1,8 +1,8 @@ -from typing import Dict, List, Protocol +from typing import Dict, Protocol +from multiversx_sdk.builders.transaction_builder import TransactionBuilder from multiversx_sdk.core.address import Address from multiversx_sdk.core.transaction import Transaction -from multiversx_sdk.builders.transaction_builder import TransactionBuilder class IConfig(Protocol): @@ -86,8 +86,8 @@ def create_transaction_for_unguarding_account(self, sender: Address) -> Transact return transaction - def _compute_data_parts_for_saving_key_value(self, key_value_pairs: Dict[bytes, bytes]) -> List[str]: - data_parts: List[str] = [] + def _compute_data_parts_for_saving_key_value(self, key_value_pairs: Dict[bytes, bytes]) -> list[str]: + data_parts: list[str] = [] for key, value in key_value_pairs.items(): data_parts.extend([key.hex(), value.hex()]) diff --git a/multiversx_sdk/core/errors.py b/multiversx_sdk/core/errors.py index 0e97d14e..ddb42799 100644 --- a/multiversx_sdk/core/errors.py +++ b/multiversx_sdk/core/errors.py @@ -11,11 +11,6 @@ def __init__(self, address: Any) -> None: super().__init__(f"Bad address: {address}") -class ListsLengthMismatchError(Exception): - def __init__(self, message: str) -> None: - super().__init__(message) - - class NotEnoughGasError(Exception): def __init__(self, gas_limit: int) -> None: super().__init__(f"Not enough gas provided: {gas_limit}") @@ -31,22 +26,6 @@ def __init__(self, message: str) -> None: super().__init__(message) -class InvalidInnerTransactionError(Exception): - def __init__(self, message: str) -> None: - super().__init__(message) - - class ParseTransactionOnNetworkError(Exception): def __init__(self, message: str) -> None: super().__init__(message) - - -class SmartContractQueryError(Exception): - def __init__(self, return_code: str, message: str) -> None: - super().__init__(message) - self.return_code = return_code - - -class ArgumentSerializationError(Exception): - def __init__(self, message: str = "Unable to encode arguments: unsupported format or missing ABI file") -> None: - super().__init__(message) diff --git a/multiversx_sdk/core/transaction_events_parser.py b/multiversx_sdk/core/transaction_events_parser.py index 7d456e52..309b5dea 100644 --- a/multiversx_sdk/core/transaction_events_parser.py +++ b/multiversx_sdk/core/transaction_events_parser.py @@ -1,16 +1,11 @@ from types import SimpleNamespace -from typing import Protocol +from multiversx_sdk.abi.abi import Abi from multiversx_sdk.core.transaction_on_network import TransactionEvent -class IAbi(Protocol): - def decode_event(self, event_name: str, topics: list[bytes], additional_data: list[bytes]) -> SimpleNamespace: - ... - - class TransactionEventsParser: - def __init__(self, abi: IAbi, first_topic_as_identifier: bool = True) -> None: + def __init__(self, abi: Abi, first_topic_as_identifier: bool = True) -> None: self.abi = abi # By default, we consider that the first topic is the event identifier. # This is true for log entries emitted by smart contracts: diff --git a/multiversx_sdk/delegation/delegation_transactions_factory.py b/multiversx_sdk/delegation/delegation_transactions_factory.py index eb23db83..3859bc73 100644 --- a/multiversx_sdk/delegation/delegation_transactions_factory.py +++ b/multiversx_sdk/delegation/delegation_transactions_factory.py @@ -3,11 +3,11 @@ from multiversx_sdk.abi import Serializer from multiversx_sdk.abi.biguint_value import BigUIntValue from multiversx_sdk.abi.string_value import StringValue +from multiversx_sdk.builders.transaction_builder import TransactionBuilder from multiversx_sdk.core import Address, Transaction from multiversx_sdk.core.constants import DELEGATION_MANAGER_SC_ADDRESS -from multiversx_sdk.core.errors import ListsLengthMismatchError from multiversx_sdk.core.interfaces import IValidatorPublicKey -from multiversx_sdk.builders.transaction_builder import TransactionBuilder +from multiversx_sdk.delegation.errors import ListsLengthMismatchError class IConfig(Protocol): diff --git a/multiversx_sdk/delegation/delegation_transactions_outcome_parser.py b/multiversx_sdk/delegation/delegation_transactions_outcome_parser.py index f86a9f32..a2f7fbfd 100644 --- a/multiversx_sdk/delegation/delegation_transactions_outcome_parser.py +++ b/multiversx_sdk/delegation/delegation_transactions_outcome_parser.py @@ -1,5 +1,3 @@ -from typing import List - from multiversx_sdk.core import (Address, TransactionEvent, TransactionOnNetwork, find_events_by_identifier) @@ -14,13 +12,13 @@ def __init__(self) -> None: pass def parse_create_new_delegation_contract(self, - transaction: TransactionOnNetwork) -> List[CreateNewDelegationContractOutcome]: + transaction: TransactionOnNetwork) -> list[CreateNewDelegationContractOutcome]: self._ensure_no_error(transaction.logs.events) events = find_events_by_identifier(transaction, "SCDeploy") return [CreateNewDelegationContractOutcome(self._extract_contract_address(event)) for event in events] - def _ensure_no_error(self, transaction_events: List[TransactionEvent]) -> None: + def _ensure_no_error(self, transaction_events: list[TransactionEvent]) -> None: for event in transaction_events: if event.identifier == "signalError": data = event.additional_data[0].decode()[1:] if len(event.additional_data[0]) else "" diff --git a/multiversx_sdk/delegation/errors.py b/multiversx_sdk/delegation/errors.py new file mode 100644 index 00000000..87bcbb11 --- /dev/null +++ b/multiversx_sdk/delegation/errors.py @@ -0,0 +1,3 @@ +class ListsLengthMismatchError(Exception): + def __init__(self, message: str) -> None: + super().__init__(message) diff --git a/multiversx_sdk/entrypoints/entrypoints.py b/multiversx_sdk/entrypoints/entrypoints.py index ddc1e1a3..75cb74d6 100644 --- a/multiversx_sdk/entrypoints/entrypoints.py +++ b/multiversx_sdk/entrypoints/entrypoints.py @@ -1,5 +1,6 @@ -from typing import Any, Optional, Protocol, Tuple, Union +from typing import Optional, Union +from multiversx_sdk.abi.abi import Abi from multiversx_sdk.account_management import AccountController from multiversx_sdk.accounts import Account from multiversx_sdk.core import (Address, Message, MessageComputer, @@ -21,20 +22,6 @@ from multiversx_sdk.wallet.user_verifer import UserVerifier -class IAbi(Protocol): - def encode_endpoint_input_parameters(self, endpoint_name: str, values: list[Any]) -> list[bytes]: - ... - - def encode_constructor_input_parameters(self, values: list[Any]) -> list[bytes]: - ... - - def encode_upgrade_constructor_input_parameters(self, values: list[Any]) -> list[bytes]: - ... - - def decode_endpoint_output_parameters(self, endpoint_name: str, encoded_values: list[bytes]) -> list[Any]: - ... - - class NetworkEntrypoint: def __init__(self, network_provider_url: str, @@ -83,7 +70,7 @@ def verify_message_signature(self, message: Message) -> bool: def recall_account_nonce(self, address: Address) -> int: return self.network_provider.get_account(address).nonce - def send_transactions(self, transactions: list[Transaction]) -> Tuple[int, list[bytes]]: + def send_transactions(self, transactions: list[Transaction]) -> tuple[int, list[bytes]]: """ Sends multiple transactions. @@ -91,7 +78,7 @@ def send_transactions(self, transactions: list[Transaction]) -> Tuple[int, list[ transactions (list[Transaction]): An iterable containing multiple transactions (e.g. a list of transactions). Returns: - Tuple (int, list[bytes]): The integer indicates the total number of transactions sent, while the list contains the transactions hashes. If a transaction is not sent, the hash is empty. + tuple (int, list[bytes]): The integer indicates the total number of transactions sent, while the list contains the transactions hashes. If a transaction is not sent, the hash is empty. """ return self.network_provider.send_transactions(transactions) @@ -113,7 +100,7 @@ def create_account_controller(self) -> AccountController: def create_relayed_controller(self) -> RelayedController: return RelayedController(self.chain_id) - def create_smart_contract_controller(self, abi: Optional[IAbi] = None) -> SmartContractController: + def create_smart_contract_controller(self, abi: Optional[Abi] = None) -> SmartContractController: return SmartContractController(self.chain_id, self.network_provider, abi) def create_token_management_controller(self) -> TokenManagementController: diff --git a/multiversx_sdk/entrypoints/entrypoints_test.py b/multiversx_sdk/entrypoints/entrypoints_test.py index ea7f4677..c56090d5 100644 --- a/multiversx_sdk/entrypoints/entrypoints_test.py +++ b/multiversx_sdk/entrypoints/entrypoints_test.py @@ -66,7 +66,7 @@ def test_contract_flow(self): tx_hash = self.entrypoint.send_transaction(transaction) self.entrypoint.await_completed_transaction(tx_hash) - query_result = controller.query_contract( + query_result = controller.query( contract=contract_address, function="getSum", arguments=[] diff --git a/multiversx_sdk/entrypoints/errors.py b/multiversx_sdk/entrypoints/errors.py index 34a8ad9b..dec8d87c 100644 --- a/multiversx_sdk/entrypoints/errors.py +++ b/multiversx_sdk/entrypoints/errors.py @@ -1,8 +1,3 @@ -class BadUsageError(Exception): - def __init__(self, message: str) -> None: - super().__init__(message) - - class InvalidNetworkProviderKindError(Exception): def __init__(self) -> None: super().__init__("Invalid network provider kind. Choose between `api` and `proxy`.") diff --git a/multiversx_sdk/network_providers/api_network_provider_test.py b/multiversx_sdk/network_providers/api_network_provider_test.py index 5a9a0844..2548ffbe 100644 --- a/multiversx_sdk/network_providers/api_network_provider_test.py +++ b/multiversx_sdk/network_providers/api_network_provider_test.py @@ -267,7 +267,7 @@ def test_get_sc_invoking_tx(self): def test_query_contract(self): query = SmartContractQuery( - contract="erd1qqqqqqqqqqqqqpgqqy34h7he2ya6qcagqre7ur7cc65vt0mxrc8qnudkr4", + contract=Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqqy34h7he2ya6qcagqre7ur7cc65vt0mxrc8qnudkr4"), function="getSum", arguments=[], ) diff --git a/multiversx_sdk/network_providers/http_resources.py b/multiversx_sdk/network_providers/http_resources.py index 9cb36e98..6e324d2a 100644 --- a/multiversx_sdk/network_providers/http_resources.py +++ b/multiversx_sdk/network_providers/http_resources.py @@ -20,14 +20,14 @@ def smart_contract_query_to_vm_query_request(query: SmartContractQuery) -> dict[str, Any]: request: dict[str, Any] = { - "scAddress": query.contract, + "scAddress": query.contract.to_bech32(), "funcName": query.function, "value": str(query.value if query.value else 0), "args": [arg.hex() for arg in query.arguments] } if query.caller: - request["caller"] = query.caller + request["caller"] = query.caller.to_bech32() return request diff --git a/multiversx_sdk/network_providers/proxy_network_provider.py b/multiversx_sdk/network_providers/proxy_network_provider.py index 2703a4d2..5a30900b 100644 --- a/multiversx_sdk/network_providers/proxy_network_provider.py +++ b/multiversx_sdk/network_providers/proxy_network_provider.py @@ -247,7 +247,7 @@ def get_definition_of_fungible_token(self, token_identifier: str) -> FungibleTok """Fetches the definition of a fungible token.""" encoded_identifier = token_identifier.encode() query = SmartContractQuery( - contract=ESDT_CONTRACT_ADDRESS, + contract=Address.new_from_bech32(ESDT_CONTRACT_ADDRESS), function="getTokenProperties", arguments=[encoded_identifier], ) @@ -261,7 +261,7 @@ def get_definition_of_tokens_collection(self, collection_name: str) -> TokensCol """Fetches the definition of a tokens collection.""" encoded_identifier = collection_name.encode() query = SmartContractQuery( - contract=ESDT_CONTRACT_ADDRESS, + contract=Address.new_from_bech32(ESDT_CONTRACT_ADDRESS), function="getTokenProperties", arguments=[encoded_identifier], ) diff --git a/multiversx_sdk/network_providers/proxy_network_provider_test.py b/multiversx_sdk/network_providers/proxy_network_provider_test.py index fd6f0db8..ac789793 100644 --- a/multiversx_sdk/network_providers/proxy_network_provider_test.py +++ b/multiversx_sdk/network_providers/proxy_network_provider_test.py @@ -151,7 +151,7 @@ def test_get_transaction_status(self): def test_query_contract(self): query = SmartContractQuery( - contract="erd1qqqqqqqqqqqqqpgqqy34h7he2ya6qcagqre7ur7cc65vt0mxrc8qnudkr4", + contract=Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqqy34h7he2ya6qcagqre7ur7cc65vt0mxrc8qnudkr4"), function="getSum", arguments=[] ) diff --git a/multiversx_sdk/relayed/errors.py b/multiversx_sdk/relayed/errors.py new file mode 100644 index 00000000..bc2a8b03 --- /dev/null +++ b/multiversx_sdk/relayed/errors.py @@ -0,0 +1,3 @@ +class InvalidInnerTransactionError(Exception): + def __init__(self, message: str) -> None: + super().__init__(message) diff --git a/multiversx_sdk/relayed/relayed_transactions_factory.py b/multiversx_sdk/relayed/relayed_transactions_factory.py index 753e59ec..ce84dce4 100644 --- a/multiversx_sdk/relayed/relayed_transactions_factory.py +++ b/multiversx_sdk/relayed/relayed_transactions_factory.py @@ -5,7 +5,7 @@ from multiversx_sdk.abi import AddressValue, BigUIntValue, Serializer from multiversx_sdk.abi.bytes_value import BytesValue from multiversx_sdk.core import Address, Transaction -from multiversx_sdk.core.errors import InvalidInnerTransactionError +from multiversx_sdk.relayed.errors import InvalidInnerTransactionError class IConfig(Protocol): diff --git a/multiversx_sdk/relayed/relayed_transactions_factory_test.py b/multiversx_sdk/relayed/relayed_transactions_factory_test.py index b5e624bc..e6c00b2a 100644 --- a/multiversx_sdk/relayed/relayed_transactions_factory_test.py +++ b/multiversx_sdk/relayed/relayed_transactions_factory_test.py @@ -1,9 +1,9 @@ import pytest from multiversx_sdk.core import Address, Transaction, TransactionComputer -from multiversx_sdk.core.errors import InvalidInnerTransactionError from multiversx_sdk.core.transactions_factory_config import \ TransactionsFactoryConfig +from multiversx_sdk.relayed.errors import InvalidInnerTransactionError from multiversx_sdk.relayed.relayed_transactions_factory import \ RelayedTransactionsFactory from multiversx_sdk.testutils.wallets import load_wallets diff --git a/multiversx_sdk/smart_contracts/errors.py b/multiversx_sdk/smart_contracts/errors.py new file mode 100644 index 00000000..0f4172f6 --- /dev/null +++ b/multiversx_sdk/smart_contracts/errors.py @@ -0,0 +1,10 @@ + +class SmartContractQueryError(Exception): + def __init__(self, return_code: str, message: str) -> None: + super().__init__(message) + self.return_code = return_code + + +class ArgumentSerializationError(Exception): + def __init__(self, message: str = "Unable to encode arguments: unsupported format or missing ABI file") -> None: + super().__init__(message) diff --git a/multiversx_sdk/smart_contracts/smart_contract_controller.py b/multiversx_sdk/smart_contracts/smart_contract_controller.py index 749d71a0..196d60e4 100644 --- a/multiversx_sdk/smart_contracts/smart_contract_controller.py +++ b/multiversx_sdk/smart_contracts/smart_contract_controller.py @@ -1,22 +1,25 @@ from pathlib import Path from typing import Any, Optional, Protocol, Sequence, Union +from multiversx_sdk.abi.abi import Abi +from multiversx_sdk.abi.serializer import Serializer +from multiversx_sdk.abi.typesystem import (is_list_of_bytes, + is_list_of_typed_values) from multiversx_sdk.core import (Address, TokenTransfer, Transaction, TransactionComputer, TransactionOnNetwork) from multiversx_sdk.core.interfaces import IAccount from multiversx_sdk.core.transactions_factory_config import \ TransactionsFactoryConfig from multiversx_sdk.network_providers.resources import AwaitingOptions -from multiversx_sdk.smart_contracts.smart_contract_queries_controller import \ - SmartContractQueriesController +from multiversx_sdk.smart_contracts.errors import SmartContractQueryError from multiversx_sdk.smart_contracts.smart_contract_query import ( SmartContractQuery, SmartContractQueryResponse) from multiversx_sdk.smart_contracts.smart_contract_transactions_factory import \ SmartContractTransactionsFactory from multiversx_sdk.smart_contracts.smart_contract_transactions_outcome_parser import \ SmartContractTransactionsOutcomeParser -from multiversx_sdk.smart_contracts.smart_contract_transactions_outcome_parser_types import \ - SmartContractDeployOutcome +from multiversx_sdk.smart_contracts.smart_contract_transactions_outcome_parser_types import ( + ParsedSmartContractCallOutcome, SmartContractDeployOutcome) class INetworkProvider(Protocol): @@ -27,32 +30,15 @@ def await_transaction_completed(self, transaction_hash: Union[str, bytes], optio ... -class IAbi(Protocol): - def encode_endpoint_input_parameters(self, endpoint_name: str, values: list[Any]) -> list[bytes]: - ... - - def encode_constructor_input_parameters(self, values: list[Any]) -> list[bytes]: - ... - - def encode_upgrade_constructor_input_parameters(self, values: list[Any]) -> list[bytes]: - ... - - def decode_endpoint_output_parameters(self, endpoint_name: str, encoded_values: list[bytes]) -> list[Any]: - ... - - class SmartContractController: - def __init__(self, chain_id: str, network_provider: INetworkProvider, abi: Optional[IAbi] = None) -> None: + def __init__(self, chain_id: str, network_provider: INetworkProvider, abi: Optional[Abi] = None) -> None: self.abi = abi self.factory = SmartContractTransactionsFactory( TransactionsFactoryConfig(chain_id), abi=self.abi) self.parser = SmartContractTransactionsOutcomeParser() - self.query_controller = SmartContractQueriesController( - network_provider=network_provider, - abi=self.abi - ) self.network_provider = network_provider self.tx_computer = TransactionComputer() + self.serializer = Serializer() def create_transaction_for_deploy(self, sender: IAccount, @@ -144,22 +130,72 @@ def create_transaction_for_execute(self, return transaction - def parse_execute(self, transaction_on_network: TransactionOnNetwork, function: Optional[str] = None) -> list[Any]: - raise NotImplementedError("This method is not yet implemented") + def parse_execute(self, transaction_on_network: TransactionOnNetwork, function: Optional[str] = None) -> ParsedSmartContractCallOutcome: + return self.parser.parse_execute(transaction_on_network, function) + + def await_completed_execute(self, transaction_hash: Union[str, bytes]) -> ParsedSmartContractCallOutcome: + transaction = self.network_provider.await_transaction_completed(transaction_hash) + return self.parse_execute(transaction, transaction.function) + + def query(self, + contract: Address, + function: str, + arguments: list[Any], + caller: Optional[Address] = None, + value: Optional[int] = None) -> list[Any]: + """It calls `create_query()`, `run_query()` and `parse_query_response()` in one go.""" + query = self.create_query( + contract=contract, + function=function, + arguments=arguments, + caller=caller, + value=value + ) + + query_response = self.run_query(query) + self._raise_for_status(query_response) + return self.parse_query_response(query_response) - def await_completed_execute(self, transaction_hash: Union[str, bytes]) -> list[Any]: - raise NotImplementedError("This feature is not yet implemented") + def _raise_for_status(self, query_response: SmartContractQueryResponse): + is_ok = query_response.return_code == "ok" + if not is_ok: + raise SmartContractQueryError(query_response.return_code, query_response.return_message) - def query_contract(self, - contract: Address, - function: str, - arguments: list[Any], - caller: Optional[Address] = None, - value: Optional[int] = None) -> list[Any]: - return self.query_controller.query( - contract=contract.to_bech32(), + def create_query(self, + contract: Address, + function: str, + arguments: list[Any], + caller: Optional[Address] = None, + value: Optional[int] = None) -> SmartContractQuery: + prepared_arguments = self._encode_arguments(function, arguments) + + return SmartContractQuery( + contract=contract, function=function, - arguments=arguments, - caller=caller.to_bech32() if caller else None, + arguments=prepared_arguments, + caller=caller, value=value ) + + def _encode_arguments(self, function_name: str, args: list[Any]) -> list[bytes]: + if self.abi: + return self.abi.encode_endpoint_input_parameters(function_name, args) + + if is_list_of_typed_values(args): + return self.serializer.serialize_to_parts(args) + + if is_list_of_bytes(args): + return args + + raise Exception("Can't serialize arguments") + + def run_query(self, query: SmartContractQuery) -> SmartContractQueryResponse: + return self.network_provider.query_contract(query) + + def parse_query_response(self, response: SmartContractQueryResponse) -> list[Any]: + encoded_values = response.return_data_parts + + if self.abi: + return self.abi.decode_endpoint_output_parameters(response.function, encoded_values) + + return encoded_values diff --git a/multiversx_sdk/smart_contracts/smart_contract_queries_controller_test.py b/multiversx_sdk/smart_contracts/smart_contract_controller_test.py similarity index 50% rename from multiversx_sdk/smart_contracts/smart_contract_queries_controller_test.py rename to multiversx_sdk/smart_contracts/smart_contract_controller_test.py index d25783b9..fec4b884 100644 --- a/multiversx_sdk/smart_contracts/smart_contract_queries_controller_test.py +++ b/multiversx_sdk/smart_contracts/smart_contract_controller_test.py @@ -3,12 +3,16 @@ import pytest from multiversx_sdk.abi.abi import Abi -from multiversx_sdk.abi.small_int_values import U64Value +from multiversx_sdk.abi.biguint_value import BigUIntValue +from multiversx_sdk.abi.small_int_values import U32Value, U64Value from multiversx_sdk.abi.string_value import StringValue +from multiversx_sdk.accounts.account import Account +from multiversx_sdk.core.address import Address +from multiversx_sdk.core.constants import CONTRACT_DEPLOY_ADDRESS from multiversx_sdk.network_providers.api_network_provider import \ ApiNetworkProvider -from multiversx_sdk.smart_contracts.smart_contract_queries_controller import \ - SmartContractQueriesController +from multiversx_sdk.smart_contracts.smart_contract_controller import \ + SmartContractController from multiversx_sdk.smart_contracts.smart_contract_query import ( SmartContractQuery, SmartContractQueryResponse) from multiversx_sdk.testutils.mock_network_provider import MockNetworkProvider @@ -16,10 +20,73 @@ class TestSmartContractQueriesController: testdata = Path(__file__).parent.parent / "testutils" / "testdata" + testwallets = Path(__file__).parent.parent / "testutils" / "testwallets" + alice = Account.new_from_pem(testwallets / "alice.pem") + bytecode = (testdata / "adder.wasm").read_bytes() + abi = Abi.load(testdata / "adder.abi.json") + + def test_create_transaction_for_deploy(self): + controller = SmartContractController(chain_id="D", network_provider=MockNetworkProvider(), abi=self.abi) + gas_limit = 6000000 + + transaction = controller.create_transaction_for_deploy( + sender=self.alice, + nonce=self.alice.get_nonce_then_increment(), + bytecode=self.bytecode, + gas_limit=gas_limit, + arguments=[BigUIntValue(1)] + ) + + assert transaction.sender.to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction.receiver.to_bech32() == CONTRACT_DEPLOY_ADDRESS + assert transaction.data == f"{self.bytecode.hex()}@0500@0504@01".encode() + assert transaction.gas_limit == gas_limit + assert transaction.value == 0 + + def test_create_transaction_for_execute(self): + controller = SmartContractController(chain_id="D", network_provider=MockNetworkProvider(), abi=self.abi) + contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4") + function = "add" + gas_limit = 6000000 + + transaction = controller.create_transaction_for_execute( + sender=self.alice, + nonce=self.alice.get_nonce_then_increment(), + contract=contract, + function=function, + gas_limit=gas_limit, + arguments=[U32Value(7)] + ) + + assert transaction.sender.to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4" + assert transaction.gas_limit == gas_limit + assert transaction.data.decode() == "add@07" + assert transaction.value == 0 + + def test_create_transaction_for_upgrade(self): + controller = SmartContractController(chain_id="D", network_provider=MockNetworkProvider(), abi=self.abi) + contract_address = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4") + gas_limit = 6000000 + + transaction = controller.create_transaction_for_upgrade( + sender=self.alice, + nonce=self.alice.get_nonce_then_increment(), + contract=contract_address, + bytecode=self.bytecode, + gas_limit=gas_limit, + arguments=[BigUIntValue(0)] + ) + + assert transaction.sender.to_bech32() == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4" + assert transaction.data == f"upgradeContract@{self.bytecode.hex()}@0504@".encode() + assert transaction.gas_limit == gas_limit + assert transaction.value == 0 def test_create_query_without_arguments(self): - controller = SmartContractQueriesController(MockNetworkProvider()) - contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx" + controller = SmartContractController(chain_id="D", network_provider=MockNetworkProvider()) + contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx") function = "getSum" query = controller.create_query( @@ -35,8 +102,8 @@ def test_create_query_without_arguments(self): assert query.value is None def test_create_query_with_arguments(self): - controller = SmartContractQueriesController(MockNetworkProvider()) - contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx" + controller = SmartContractController(chain_id="D", network_provider=MockNetworkProvider()) + contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx") function = "getSum" query = controller.create_query( @@ -53,8 +120,8 @@ def test_create_query_with_arguments(self): def test_create_query_with_arguments_with_abi(self): abi = Abi.load(self.testdata / "lottery-esdt.abi.json") - controller = SmartContractQueriesController(MockNetworkProvider(), abi) - contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx" + controller = SmartContractController(chain_id="D", network_provider=MockNetworkProvider(), abi=abi) + contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx") function = "getLotteryInfo" query = controller.create_query( @@ -79,7 +146,7 @@ def test_create_query_with_arguments_with_abi(self): def test_run_query_with_mock_provider(self): network_provider = MockNetworkProvider() - controller = SmartContractQueriesController(network_provider) + controller = SmartContractController(chain_id="D", network_provider=network_provider) contract_query_response = SmartContractQueryResponse( function="bar", @@ -91,7 +158,7 @@ def test_run_query_with_mock_provider(self): network_provider.mock_query_contract_on_function("bar", contract_query_response) query = SmartContractQuery( - contract="erd1qqqqqqqqqqqqqpgqvc7gdl0p4s97guh498wgz75k8sav6sjfjlwqh679jy", + contract=Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqvc7gdl0p4s97guh498wgz75k8sav6sjfjlwqh679jy"), function="bar", arguments=[] ) @@ -101,7 +168,7 @@ def test_run_query_with_mock_provider(self): assert response.return_data_parts == ["abba".encode()] def test_parse_query_response(self): - controller = SmartContractQueriesController(MockNetworkProvider()) + controller = SmartContractController(chain_id="D", network_provider=MockNetworkProvider()) response = SmartContractQueryResponse( function="bar", @@ -115,7 +182,7 @@ def test_parse_query_response(self): def test_parse_query_response_with_abi(self): abi = Abi.load(self.testdata / "lottery-esdt.abi.json") - controller = SmartContractQueriesController(MockNetworkProvider(), abi) + controller = SmartContractController(chain_id="D", network_provider=MockNetworkProvider(), abi=abi) response = SmartContractQueryResponse( function="getLotteryInfo", @@ -137,8 +204,8 @@ def test_parse_query_response_with_abi(self): @pytest.mark.networkInteraction def test_run_query_on_network(self): provider = ApiNetworkProvider("https://devnet-api.multiversx.com") - controller = SmartContractQueriesController(provider) - contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx" + controller = SmartContractController(chain_id="D", network_provider=provider) + contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx") function = "getSum" query = controller.create_query( @@ -155,8 +222,8 @@ def test_run_query_on_network(self): @pytest.mark.networkInteraction def test_query_on_network(self): provider = ApiNetworkProvider("https://devnet-api.multiversx.com") - controller = SmartContractQueriesController(provider) - contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx" + controller = SmartContractController(chain_id="D", network_provider=provider) + contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx") function = "getSum" return_data_parts = controller.query( diff --git a/multiversx_sdk/smart_contracts/smart_contract_queries_controller.py b/multiversx_sdk/smart_contracts/smart_contract_queries_controller.py deleted file mode 100644 index 67b6d236..00000000 --- a/multiversx_sdk/smart_contracts/smart_contract_queries_controller.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import Any, Optional, Protocol - -from multiversx_sdk.abi import Serializer -from multiversx_sdk.abi.typesystem import (is_list_of_bytes, - is_list_of_typed_values) -from multiversx_sdk.core.errors import SmartContractQueryError -from multiversx_sdk.smart_contracts.smart_contract_query import ( - SmartContractQuery, SmartContractQueryResponse) - - -class INetworkProvider(Protocol): - def query_contract(self, query: SmartContractQuery) -> SmartContractQueryResponse: - ... - - -class IAbi(Protocol): - def encode_endpoint_input_parameters(self, endpoint_name: str, values: list[Any]) -> list[bytes]: - ... - - def decode_endpoint_output_parameters(self, endpoint_name: str, encoded_values: list[bytes]) -> list[Any]: - ... - - -class SmartContractQueriesController: - def __init__(self, network_provider: INetworkProvider, abi: Optional[IAbi] = None) -> None: - self.network_provider = network_provider - self.abi = abi - self.serializer = Serializer() - - def query( - self, - contract: str, - function: str, - arguments: list[Any], - caller: Optional[str] = None, - value: Optional[int] = None - ): - query = self.create_query( - contract=contract, - function=function, - arguments=arguments, - caller=caller, - value=value - ) - - query_response = self.run_query(query) - self._raise_for_status(query_response) - return self.parse_query_response(query_response) - - def _raise_for_status(self, query_response: SmartContractQueryResponse): - is_ok = query_response.return_code == "ok" - if not is_ok: - raise SmartContractQueryError(query_response.return_code, query_response.return_message) - - def create_query( - self, - contract: str, - function: str, - arguments: list[Any], - caller: Optional[str] = None, - value: Optional[int] = None - ) -> SmartContractQuery: - prepared_arguments = self._encode_arguments(function, arguments) - - return SmartContractQuery( - contract=contract, - function=function, - arguments=prepared_arguments, - caller=caller, - value=value - ) - - def _encode_arguments(self, function_name: str, args: list[Any]) -> list[bytes]: - if self.abi: - return self.abi.encode_endpoint_input_parameters(function_name, args) - - if is_list_of_typed_values(args): - return self.serializer.serialize_to_parts(args) - - if is_list_of_bytes(args): - return args - - raise Exception("Can't serialize arguments") - - def run_query(self, query: SmartContractQuery) -> SmartContractQueryResponse: - return self.network_provider.query_contract(query) - - def parse_query_response(self, response: SmartContractQueryResponse) -> list[Any]: - encoded_values = response.return_data_parts - - if self.abi: - return self.abi.decode_endpoint_output_parameters(response.function, encoded_values) - - return encoded_values diff --git a/multiversx_sdk/smart_contracts/smart_contract_query.py b/multiversx_sdk/smart_contracts/smart_contract_query.py index 985b9eb1..bc499555 100644 --- a/multiversx_sdk/smart_contracts/smart_contract_query.py +++ b/multiversx_sdk/smart_contracts/smart_contract_query.py @@ -1,12 +1,14 @@ from typing import Optional +from multiversx_sdk.core.address import Address + class SmartContractQuery: def __init__(self, - contract: str, + contract: Address, function: str, arguments: list[bytes], - caller: Optional[str] = None, + caller: Optional[Address] = None, value: Optional[int] = None) -> None: self.contract = contract self.function = function diff --git a/multiversx_sdk/smart_contracts/smart_contract_transaction_factory_test.py b/multiversx_sdk/smart_contracts/smart_contract_transaction_factory_test.py index d6363a9a..68a09df2 100644 --- a/multiversx_sdk/smart_contracts/smart_contract_transaction_factory_test.py +++ b/multiversx_sdk/smart_contracts/smart_contract_transaction_factory_test.py @@ -7,10 +7,10 @@ from multiversx_sdk.abi.small_int_values import U32Value from multiversx_sdk.core.address import Address from multiversx_sdk.core.constants import CONTRACT_DEPLOY_ADDRESS -from multiversx_sdk.core.errors import ArgumentSerializationError from multiversx_sdk.core.tokens import Token, TokenTransfer from multiversx_sdk.core.transactions_factory_config import \ TransactionsFactoryConfig +from multiversx_sdk.smart_contracts.errors import ArgumentSerializationError from multiversx_sdk.smart_contracts.smart_contract_transactions_factory import \ SmartContractTransactionsFactory diff --git a/multiversx_sdk/smart_contracts/smart_contract_transactions_factory.py b/multiversx_sdk/smart_contracts/smart_contract_transactions_factory.py index a13aa6fa..f8548f44 100644 --- a/multiversx_sdk/smart_contracts/smart_contract_transactions_factory.py +++ b/multiversx_sdk/smart_contracts/smart_contract_transactions_factory.py @@ -1,6 +1,7 @@ from pathlib import Path from typing import Any, Optional, Protocol, Sequence, Union +from multiversx_sdk.abi.abi import Abi from multiversx_sdk.abi.bytes_value import BytesValue from multiversx_sdk.abi.code_metadata_value import CodeMetadataValue from multiversx_sdk.abi.serializer import Serializer @@ -13,7 +14,7 @@ TokenTransfer, Transaction) from multiversx_sdk.core.constants import (CONTRACT_DEPLOY_ADDRESS, VM_TYPE_WASM_VM) -from multiversx_sdk.core.errors import ArgumentSerializationError +from multiversx_sdk.smart_contracts.errors import ArgumentSerializationError class IConfig(Protocol): @@ -24,19 +25,8 @@ class IConfig(Protocol): gas_limit_change_owner_address: int -class IAbi(Protocol): - def encode_endpoint_input_parameters(self, endpoint_name: str, values: list[Any]) -> list[bytes]: - ... - - def encode_constructor_input_parameters(self, values: list[Any]) -> list[bytes]: - ... - - def encode_upgrade_constructor_input_parameters(self, values: list[Any]) -> list[bytes]: - ... - - class SmartContractTransactionsFactory: - def __init__(self, config: IConfig, abi: Optional[IAbi] = None) -> None: + def __init__(self, config: IConfig, abi: Optional[Abi] = None) -> None: self.config = config self.abi = abi self.serializer = Serializer() diff --git a/multiversx_sdk/smart_contracts/smart_contract_transactions_outcome_parser.py b/multiversx_sdk/smart_contracts/smart_contract_transactions_outcome_parser.py index 26204c59..2b0f996f 100644 --- a/multiversx_sdk/smart_contracts/smart_contract_transactions_outcome_parser.py +++ b/multiversx_sdk/smart_contracts/smart_contract_transactions_outcome_parser.py @@ -1,6 +1,7 @@ from enum import Enum -from typing import Any, Optional, Protocol, Union +from typing import Optional, Union +from multiversx_sdk.abi.abi import Abi from multiversx_sdk.core import (Address, SmartContractResult, TransactionEvent, TransactionOnNetwork, find_events_by_identifier) @@ -10,11 +11,6 @@ SmartContractDeployOutcome) -class IAbi(Protocol): - def decode_endpoint_output_parameters(self, endpoint_name: str, encoded_values: list[bytes]) -> list[Any]: - ... - - class Events(Enum): SCDeploy = "SCDeploy" SignalError = "signalError" @@ -36,7 +32,7 @@ def __init__(self, class SmartContractTransactionsOutcomeParser: - def __init__(self, abi: Optional[IAbi] = None) -> None: + def __init__(self, abi: Optional[Abi] = None) -> None: self.abi = abi def parse_deploy(self, transaction: TransactionOnNetwork) -> SmartContractDeployOutcome: diff --git a/multiversx_sdk/testutils/mock_network_provider.py b/multiversx_sdk/testutils/mock_network_provider.py index 70121d00..f2645430 100644 --- a/multiversx_sdk/testutils/mock_network_provider.py +++ b/multiversx_sdk/testutils/mock_network_provider.py @@ -1,6 +1,6 @@ import threading import time -from typing import Any, Callable, Union +from typing import Any, Callable, Optional, Union from multiversx_sdk.core.address import Address from multiversx_sdk.core.transaction import Transaction @@ -9,7 +9,8 @@ TransactionLogs, TransactionOnNetwork) from multiversx_sdk.core.transaction_status import TransactionStatus -from multiversx_sdk.network_providers.resources import AccountOnNetwork +from multiversx_sdk.network_providers.resources import (AccountOnNetwork, + AwaitingOptions) from multiversx_sdk.smart_contracts.smart_contract_query import ( SmartContractQuery, SmartContractQueryResponse) from multiversx_sdk.testutils.mock_transaction_on_network import \ @@ -156,6 +157,9 @@ def query_contract(self, query: SmartContractQuery) -> SmartContractQueryRespons raise Exception("No query response to return") + def await_transaction_completed(self, transaction_hash: Union[str, bytes], options: Optional[AwaitingOptions] = None) -> TransactionOnNetwork: + ... + class QueryContractResponder: def __init__(self, matches: Callable[[SmartContractQuery], bool], response: SmartContractQueryResponse) -> None: