Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codecs (part 7): integration of serializer & ABI into queries controller and contract transactions factory #56

Merged
merged 1 commit into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions multiversx_sdk/core/smart_contract_queries_controller.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from typing import Any, List, 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.constants import ARGS_SEPARATOR
from multiversx_sdk.core.errors import SmartContractQueryError
from multiversx_sdk.core.smart_contract_query import (
SmartContractQuery, SmartContractQueryResponse)
Expand All @@ -10,9 +14,19 @@ def run_query(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, query_runner: IQueryRunner) -> None:
def __init__(self, query_runner: IQueryRunner, abi: Optional[IAbi] = None) -> None:
self.query_runner = query_runner
self.abi = abi
self.serializer = Serializer(parts_separator=ARGS_SEPARATOR)

def query(
self,
Expand Down Expand Up @@ -43,21 +57,40 @@ def create_query(
self,
contract: str,
function: str,
arguments: List[bytes],
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=arguments,
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 ValueError("cannot encode arguments: when ABI is not available, they must be either typed values or buffers")
Copy link
Collaborator

Choose a reason for hiding this comment

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

...typed values or bytes, maybe? (optional)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will be applied in the very next PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed here: #57.


def run_query(self, query: SmartContractQuery) -> SmartContractQueryResponse:
query_response = self.query_runner.run_query(query)
return query_response

def parse_query_response(self, response: SmartContractQueryResponse) -> List[Any]:
return response.return_data_parts
encoded_values = response.return_data_parts

if self.abi:
return self.abi.decode_endpoint_output_parameters(response.function, encoded_values)

return encoded_values
82 changes: 71 additions & 11 deletions multiversx_sdk/core/smart_contract_queries_controller_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import base64
from pathlib import Path

import pytest

from multiversx_sdk.abi.abi import Abi
from multiversx_sdk.abi.string_value import StringValue
from multiversx_sdk.adapters.query_runner_adapter import QueryRunnerAdapter
from multiversx_sdk.core.codec import encode_unsigned_number
from multiversx_sdk.core.smart_contract_queries_controller import \
Expand All @@ -16,43 +19,74 @@


class TestSmartContractQueriesController:
provider = ProxyNetworkProvider("https://devnet-api.multiversx.com")
query_runner = QueryRunnerAdapter(network_provider=provider)
controller = SmartContractQueriesController(query_runner=query_runner)
testdata = Path(__file__).parent.parent / "testutils" / "testdata"

def test_create_query_without_arguments(self):
query_runner = QueryRunnerAdapter(MockNetworkProvider())
controller = SmartContractQueriesController(query_runner)
contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx"
function = "getSum"

query = self.controller.create_query(
query = controller.create_query(
contract=contract,
function=function,
arguments=[]
)

assert query.contract == contract
assert query.function == function
assert query.arguments == []
assert query.caller is None
assert query.value is None

def test_create_query_with_arguments(self):
query_runner = QueryRunnerAdapter(MockNetworkProvider())
controller = SmartContractQueriesController(query_runner)
contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx"
function = "getSum"

query = self.controller.create_query(
query = controller.create_query(
contract=contract,
function=function,
arguments=[encode_unsigned_number(7), "abba".encode()]
)

assert query.contract == contract
assert query.function == function
assert query.arguments == [encode_unsigned_number(7), "abba".encode()]
assert query.caller is None
assert query.value is None

def test_create_query_with_arguments_with_abi(self):
query_runner = QueryRunnerAdapter(MockNetworkProvider())
abi = Abi.load(self.testdata / "lottery-esdt.abi.json")
controller = SmartContractQueriesController(query_runner, abi)
contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx"
function = "getLotteryInfo"

query = controller.create_query(
contract=contract,
function=function,
arguments=["myLottery"]
)

query_with_typed = controller.create_query(
contract=contract,
function=function,
arguments=[StringValue("myLottery")]
)

assert query.contract == contract
assert query.function == function
assert query.arguments == [b"myLottery"]
assert query.caller is None
assert query.value is None

assert query_with_typed == query

def test_run_query_with_mock_provider(self):
network_provider = MockNetworkProvider()
query_runner = QueryRunnerAdapter(network_provider=network_provider)
query_runner = QueryRunnerAdapter(network_provider)
controller = SmartContractQueriesController(query_runner)

contract_query_response = ContractQueryResponse()
Expand All @@ -72,8 +106,7 @@ def test_run_query_with_mock_provider(self):
assert response.return_data_parts == ["abba".encode()]

def test_parse_query_response(self):
network_provider = MockNetworkProvider()
query_runner = QueryRunnerAdapter(network_provider=network_provider)
query_runner = QueryRunnerAdapter(MockNetworkProvider())
controller = SmartContractQueriesController(query_runner)

response = SmartContractQueryResponse(
Expand All @@ -86,28 +119,55 @@ def test_parse_query_response(self):
parsed = controller.parse_query_response(response)
assert parsed == ["abba".encode()]

def test_parse_query_response_with_abi(self):
query_runner = QueryRunnerAdapter(MockNetworkProvider())
abi = Abi.load(self.testdata / "lottery-esdt.abi.json")
controller = SmartContractQueriesController(query_runner, abi)

response = SmartContractQueryResponse(
function="getLotteryInfo",
return_code="ok",
return_message="ok",
return_data_parts=[bytes.fromhex("0000000b6c75636b792d746f6b656e000000010100000000000000005fc2b9dbffffffff00000001640000000a140ec80fa7ee88000000")]
)

[lottery_info] = controller.parse_query_response(response)
assert lottery_info.token_identifier == "lucky-token"
assert lottery_info.ticket_price == 1
assert lottery_info.tickets_left == 0
assert lottery_info.deadline == 0x000000005fc2b9db
assert lottery_info.max_entries_per_user == 0xffffffff
assert lottery_info.prize_distribution == bytes([0x64])
assert lottery_info.prize_pool == 94720000000000000000000

@pytest.mark.networkInteraction
def test_run_query_on_network(self):
provider = ProxyNetworkProvider("https://devnet-api.multiversx.com")
query_runner = QueryRunnerAdapter(provider)
controller = SmartContractQueriesController(query_runner)
contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx"
function = "getSum"

query = self.controller.create_query(
query = controller.create_query(
contract=contract,
function=function,
arguments=[]
)

query_response = self.controller.run_query(query)
query_response = controller.run_query(query)
assert query_response.return_code == "ok"
assert query_response.return_message == ""
assert query_response.return_data_parts == [b'\x05']

@pytest.mark.networkInteraction
def test_query_on_network(self):
provider = ProxyNetworkProvider("https://devnet-api.multiversx.com")
query_runner = QueryRunnerAdapter(provider)
controller = SmartContractQueriesController(query_runner)
contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx"
function = "getSum"

return_data_parts = self.controller.query(
return_data_parts = controller.query(
contract=contract,
function=function,
arguments=[]
Expand Down
12 changes: 12 additions & 0 deletions multiversx_sdk/core/smart_contract_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ def __init__(
self.caller = caller
self.value = value

def __eq__(self, other: object) -> bool:
if not isinstance(other, SmartContractQuery):
return False

return self.__dict__ == other.__dict__


class SmartContractQueryResponse:
def __init__(
Expand All @@ -29,3 +35,9 @@ def __init__(
self.return_code = return_code
self.return_message = return_message
self.return_data_parts = return_data_parts

def __eq__(self, other: object) -> bool:
if not isinstance(other, SmartContractQueryResponse):
return False

return self.__dict__ == other.__dict__
6 changes: 6 additions & 0 deletions multiversx_sdk/core/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ def __init__(self,

self.guardian = guardian or ""
self.guardian_signature = guardian_signature or bytes()

def __eq__(self, other: object) -> bool:
if not isinstance(other, Transaction):
return False

return self.__dict__ == other.__dict__
Loading
Loading