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

Smart Contract queries controller #16

Merged
merged 9 commits into from
Mar 12, 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
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
Copy link
Contributor

Choose a reason for hiding this comment

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

For reference (for reviewers):

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"
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps we should have it the reversed way? That is, by default all except networkInteraction? Just thinking out loud.

Fine this way, as well - tests of mx-chain-go is the same: all tests by default, and long tests excluded on demand 👍

```
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")
Comment on lines +18 to +19
Copy link
Contributor

Choose a reason for hiding this comment

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

use @pytest.mark.networkInteraction here also?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

there's no point in using the annotation for the class because there is no actual network interaction. In the first two tests i just create a query, then in the other tests i use a mock network provider and the last test actually interacts with the network but that one is annotated with @pytest.mark.networkInteraction

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
Loading