Skip to content

Commit

Permalink
Merge pull request #16 from multiversx/sc-query-controller
Browse files Browse the repository at this point in the history
Smart Contract queries controller
  • Loading branch information
popenta authored Mar 12, 2024
2 parents 544988c + bfd94ad commit 75ac77f
Show file tree
Hide file tree
Showing 18 changed files with 471 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[flake8]
ignore = E501
ignore = E501, E722
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ If using VSCode, restart it or follow these steps:

Run the tests as follows:

This command runs all tests:
```
pytest .
```

If you want to skip network interaction tests run:
```
pytest -m "not networkInteraction"
```
8 changes: 6 additions & 2 deletions multiversx_sdk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from multiversx_sdk.core.account import AccountNonceHolder
from multiversx_sdk.core.adapters.query_runner_adapter import \
QueryRunnerAdapter
from multiversx_sdk.core.address import (Address, AddressComputer,
AddressFactory)
from multiversx_sdk.core.code_metadata import CodeMetadata
Expand All @@ -10,6 +12,8 @@
TokenIdentifierParts, TokenTransfer)
from multiversx_sdk.core.transaction import Transaction, TransactionComputer
from multiversx_sdk.core.transaction_payload import TransactionPayload
from multiversx_sdk.core.transactions_factories.account_transactions_factory import \
AccountTransactionsFactory
from multiversx_sdk.core.transactions_factories.delegation_transactions_factory import \
DelegationTransactionsFactory
from multiversx_sdk.core.transactions_factories.relayed_transactions_factory import \
Expand Down Expand Up @@ -55,9 +59,9 @@
"DelegationTransactionsFactory", "TokenManagementTransactionsFactory",
"RegisterAndSetAllRolesTokenType", "TransactionsFactoryConfig",
"SmartContractTransactionsFactory", "TransferTransactionsFactory",
"RelayedTransactionsFactory",
"RelayedTransactionsFactory", "AccountTransactionsFactory",
"GenericError", "GenericResponse", "ApiNetworkProvider", "ProxyNetworkProvider",
"UserSigner", "Mnemonic", "UserSecretKey", "UserPublicKey", "ValidatorSecretKey",
"ValidatorPublicKey", "UserVerifier", "ValidatorSigner", "ValidatorVerifier", "ValidatorPEM",
"UserWallet", "UserPEM"
"UserWallet", "UserPEM", "QueryRunnerAdapter"
]
6 changes: 5 additions & 1 deletion multiversx_sdk/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from multiversx_sdk.core.account import AccountNonceHolder
from multiversx_sdk.core.adapters.query_runner_adapter import \
QueryRunnerAdapter
from multiversx_sdk.core.address import (Address, AddressComputer,
AddressFactory)
from multiversx_sdk.core.code_metadata import CodeMetadata
Expand All @@ -10,6 +12,8 @@
TokenIdentifierParts, TokenTransfer)
from multiversx_sdk.core.transaction import Transaction, TransactionComputer
from multiversx_sdk.core.transaction_payload import TransactionPayload
from multiversx_sdk.core.transactions_factories.account_transactions_factory import \
AccountTransactionsFactory
from multiversx_sdk.core.transactions_factories.delegation_transactions_factory import \
DelegationTransactionsFactory
from multiversx_sdk.core.transactions_factories.relayed_transactions_factory import \
Expand Down Expand Up @@ -38,5 +42,5 @@
"DelegationTransactionsFactory", "TokenManagementTransactionsFactory",
"RegisterAndSetAllRolesTokenType", "TransactionsFactoryConfig",
"SmartContractTransactionsFactory", "TransferTransactionsFactory",
"RelayedTransactionsFactory"
"RelayedTransactionsFactory", "AccountTransactionsFactory", "QueryRunnerAdapter"
]
4 changes: 4 additions & 0 deletions multiversx_sdk/core/adapters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from multiversx_sdk.core.adapters.query_runner_adapter import \
QueryRunnerAdapter

__all__ = ["QueryRunnerAdapter"]
62 changes: 62 additions & 0 deletions multiversx_sdk/core/adapters/query_runner_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from typing import List, Optional, Protocol, Sequence

from multiversx_sdk.core.address import Address
from multiversx_sdk.core.contract_query import ContractQuery
from multiversx_sdk.core.interfaces import IAddress
from multiversx_sdk.core.smart_contract_query import (
SmartContractQuery, SmartContractQueryResponse)


class IQuery(Protocol):
def get_contract(self) -> IAddress:
...

def get_function(self) -> str:
...

def get_encoded_arguments(self) -> Sequence[str]:
...

def get_caller(self) -> Optional[IAddress]:
...

def get_value(self) -> int:
...


class IQueryResponse(Protocol):
return_data: List[str]
return_code: str
return_message: str
gas_used: int

def get_return_data_parts(self) -> List[bytes]:
...


class INetworkProvider(Protocol):
def query_contract(self, query: IQuery) -> IQueryResponse:
...


class QueryRunnerAdapter:
def __init__(self, network_provider: INetworkProvider) -> None:
self.network_provider = network_provider

def run_query(self, query: SmartContractQuery) -> SmartContractQueryResponse:
adapted_query = ContractQuery(
contract=Address.new_from_bech32(query.contract),
function=query.function,
value=query.value if query.value else 0,
encoded_arguments=[arg.hex() for arg in query.arguments],
caller=Address.new_from_bech32(query.caller) if query.caller else None
)

adapted_query_response = self.network_provider.query_contract(adapted_query)

return SmartContractQueryResponse(
function=query.function,
return_code=adapted_query_response.return_code,
return_message=adapted_query_response.return_message,
return_data_parts=adapted_query_response.get_return_data_parts()
)
2 changes: 0 additions & 2 deletions multiversx_sdk/core/contract_query.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


from typing import List, Optional, Sequence

from multiversx_sdk.core.interfaces import IAddress
Expand Down
37 changes: 37 additions & 0 deletions multiversx_sdk/core/smart_contract_queries_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Any, List, Optional, Protocol

from multiversx_sdk.core.smart_contract_query import (
SmartContractQuery, SmartContractQueryResponse)


class IQueryRunner(Protocol):
def run_query(self, query: SmartContractQuery) -> SmartContractQueryResponse:
...


class SmartContractQueriesController:
def __init__(self, query_runner: IQueryRunner) -> None:
self.query_runner = query_runner

def create_query(
self,
contract: str,
function: str,
arguments: List[bytes],
caller: Optional[str] = None,
value: Optional[int] = None
) -> SmartContractQuery:
return SmartContractQuery(
contract=contract,
function=function,
arguments=arguments,
caller=caller,
value=value
)

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
103 changes: 103 additions & 0 deletions multiversx_sdk/core/smart_contract_queries_controller_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import base64

import pytest

from multiversx_sdk.core.adapters.query_runner_adapter import \
QueryRunnerAdapter
from multiversx_sdk.core.smart_contract_queries_controller import \
SmartContractQueriesController
from multiversx_sdk.core.smart_contract_query import (
SmartContractQuery, SmartContractQueryResponse)
from multiversx_sdk.network_providers.contract_query_response import \
ContractQueryResponse
from multiversx_sdk.network_providers.proxy_network_provider import \
ProxyNetworkProvider
from multiversx_sdk.testutils.mock_network_provider import MockNetworkProvider


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

def test_create_query_without_arguments(self):
contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx"
function = "getSum"

query = self.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):
contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx"
function = "getSum"

query = self.controller.create_query(
contract=contract,
function=function,
arguments=[int.to_bytes(7), "abba".encode()]
)
assert query.contract == contract
assert query.function == function
assert query.arguments == [int.to_bytes(7), "abba".encode()]
assert query.caller is None
assert query.value is None

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

contract_query_response = ContractQueryResponse()
contract_query_response.return_code = "ok"
contract_query_response.return_data = [base64.b64encode("abba".encode()).decode()]

network_provider.mock_query_contract_on_function("bar", contract_query_response)

query = SmartContractQuery(
contract="erd1qqqqqqqqqqqqqpgqvc7gdl0p4s97guh498wgz75k8sav6sjfjlwqh679jy",
function="bar",
arguments=[]
)

response = controller.run_query(query)
assert response.return_code == "ok"
assert response.return_data_parts == ["abba".encode()]

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

response = SmartContractQueryResponse(
function="bar",
return_code="ok",
return_message="ok",
return_data_parts=["abba".encode()]
)

parsed = controller.parse_query_response(response)
assert parsed == ["abba".encode()]

@pytest.mark.networkInteraction
def test_run_query_on_network(self):
contract = "erd1qqqqqqqqqqqqqpgqsnwuj85zv7t0wnxfetyqqyjvvg444lpk7uasxv8ktx"
function = "getSum"

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

query_response = self.controller.run_query(query)
assert query_response.return_code == "ok"
assert query_response.return_message == ""
assert query_response.return_data_parts == [b'\x05']
31 changes: 31 additions & 0 deletions multiversx_sdk/core/smart_contract_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import List, Optional


class SmartContractQuery:
def __init__(
self,
contract: str,
function: str,
arguments: List[bytes],
caller: Optional[str] = None,
value: Optional[int] = None
) -> None:
self.contract = contract
self.function = function
self.arguments = arguments
self.caller = caller
self.value = value


class SmartContractQueryResponse:
def __init__(
self,
function: str,
return_code: str,
return_message: str,
return_data_parts: List[bytes]
) -> None:
self.function = function
self.return_code = return_code
self.return_message = return_message
self.return_data_parts = return_data_parts
5 changes: 4 additions & 1 deletion multiversx_sdk/core/transactions_factories/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from multiversx_sdk.core.transactions_factories.account_transactions_factory import \
AccountTransactionsFactory
from multiversx_sdk.core.transactions_factories.delegation_transactions_factory import \
DelegationTransactionsFactory
from multiversx_sdk.core.transactions_factories.relayed_transactions_factory import \
Expand All @@ -18,5 +20,6 @@
"TransactionsFactoryConfig",
"SmartContractTransactionsFactory",
"TransferTransactionsFactory",
"RelayedTransactionsFactory"
"RelayedTransactionsFactory",
"AccountTransactionsFactory"
]
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def get_size(self) -> int:
return self.size


@pytest.mark.networkInteraction
class TestApi:
api = ApiNetworkProvider('https://devnet-api.multiversx.com')

Expand Down Expand Up @@ -133,7 +134,7 @@ def test_get_transactions_in_mempool_for_account(self):
def test_get_sc_invoking_tx(self):
result = self.api.get_transaction('6fe05e4ca01d42c96ae5182978a77fe49f26bcc14aac95ad4f19618173f86ddb')

assert result.is_completed == True
assert result.is_completed is True
assert len(result.contract_results.items) > 0
assert result.data == 'issue@54455354546f6b656e@54455354@016345785d8a0000@06@63616e4368616e67654f776e6572@74727565@63616e55706772616465@74727565@63616e4164645370656369616c526f6c6573@74727565'

Expand Down
35 changes: 26 additions & 9 deletions multiversx_sdk/network_providers/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,38 @@


class ISerializable(Protocol):
def to_dictionary(self) -> Dict[str, Any]: ...
def to_dictionary(self) -> Dict[str, Any]:
...


class IAddress(Protocol):
def to_bech32(self) -> str: ...
def to_bech32(self) -> str:
...

def to_hex(self) -> str:
...


class IPagination(Protocol):
def get_start(self) -> int: ...
def get_size(self) -> int: ...
def get_start(self) -> int:
...

def get_size(self) -> int:
...


class IContractQuery(Protocol):
def get_contract(self) -> IAddress: ...
def get_function(self) -> str: ...
def get_encoded_arguments(self) -> Sequence[str]: ...
def get_caller(self) -> Optional[IAddress]: ...
def get_value(self) -> int: ...
def get_contract(self) -> IAddress:
...

def get_function(self) -> str:
...

def get_encoded_arguments(self) -> Sequence[str]:
...

def get_caller(self) -> Optional[IAddress]:
...

def get_value(self) -> int:
...
Loading

0 comments on commit 75ac77f

Please sign in to comment.