diff --git a/src/Artifacts/src/tests/test_artifacts.py b/src/Artifacts/src/tests/test_artifacts.py deleted file mode 100644 index c71d1c87..00000000 --- a/src/Artifacts/src/tests/test_artifacts.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest -import index - -class TestIndex: - - @pytest.fixture - def artifacts_iterator(self): - for i in index.Artifacts: - artifacts = index.Artifacts[i] - return artifacts - - def test_fetch_artifacts_by_key(self, artifacts_iterator): - assert type(artifacts_iterator) == dict - - with pytest.raises(AssertionError): - assert type(artifacts_iterator) != dict - - - def test_fetch_arbiter_address(self): - arbiter_addr = index.Artifacts['ARBITER']['networks']['1']['address'] - assert arbiter_addr == '0x131e22ae3e90f0eeb1fb739eaa62ea0290c3fbe1' - - def test_fetch_zap_coor_kovan_address(self): - zap_coor_kovan_addr = index.Artifacts['ZAPCOORDINATOR']['networks']['42']['address'] - assert zap_coor_kovan_addr == '0xdbcac7c8bcca78fb05e96d0d2c68efb1c5922539' - - def test_fetch_registry_devnet_address(self): - registry_devnet_addr = index.Artifacts['REGISTRY']['networks']['31337']['address'] - assert registry_devnet_addr == '0xa513E6E4b8f2a923D98304ec87F64353C4D5C853' - - - def test_zap_coor_abi(self): - zap_get_contract = index.Artifacts['ZAPCOORDINATOR']['abi'] - assert type(zap_get_contract) == list \ No newline at end of file diff --git a/src/BaseContract/base_contract.py b/src/BaseContract/base_contract.py deleted file mode 100644 index fde613e6..00000000 --- a/src/BaseContract/base_contract.py +++ /dev/null @@ -1,24 +0,0 @@ -import utils -from web3 import Web3 - -class BaseContract: - def __init__(self, artifact_name, network_id, network_provider, coordinator, address, web3): - self.name = artifact_name - try: - self.artifact = artifact_name - self.network_id = network_id or '1' - coor_artifact_abi = utils.load_abi('ZapCoordinator') - coor_artifact_address = utils.load_address('ZapCoordinator', self.network_id) - self.provider = web3 or Web3(network_provider) or Web3(Web3.HTTPProvider('https://cloudflare-eth.com')) - self.coordinator = self.provider.eth.Contract(coor_artifact_abi, coordinator or coor_artifact_address) - self.contract = None - if address: - self.address = address - else: - self.address = self.artifact.networks[self.network_id].address - if coordinator: - self.coordinator.getContract() - else: - self.coordinator = self.provider.eth.Contract(self.artifact.abi, self.address) - except Exception as e: - raise e \ No newline at end of file diff --git a/src/BaseContract/test/test_base_contract.py b/src/BaseContract/test/test_base_contract.py deleted file mode 100644 index e32a0fbe..00000000 --- a/src/BaseContract/test/test_base_contract.py +++ /dev/null @@ -1,9 +0,0 @@ -# from unittest import TestCase -# from unittest.mock import Mock, MagicMock, patch -# import base_contract -# import utils - - -# class test_base_contract(TestCase): -# pass - diff --git a/src/BaseContract/utils.py b/src/BaseContract/utils.py deleted file mode 100644 index 6244e404..00000000 --- a/src/BaseContract/utils.py +++ /dev/null @@ -1,29 +0,0 @@ -import json -import os - - -# These functions replace the monorepo's artifactDir and negates the need for each -# artifact's location to be hardcoded in a seperate module. -# -# Helper function for load_abi and load_address. -def load_json(name: str) -> str: - # Adjust path below to fetch Artifacts/.json. - path = "./Artifacts/" - with open(os.path.abspath(path + f"{name}.json")) as f: - json_file = json.load(f) - return json_file - - -def load_abi(name: str) -> dict: - obj = load_json(name) - abi = obj['abi'] - return abi - -def load_address(name: str, netId: int or str) -> dict: - try: - a_dict = load_json(name) - addr = a_dict['networks'] - network_address = addr[netId]['address'] - return network_address - except Exception as e: - print('Error with: ' + str(e)) diff --git a/src/Artifacts/contracts/Arbiter.json b/src/artifacts/contracts/Arbiter.json similarity index 100% rename from src/Artifacts/contracts/Arbiter.json rename to src/artifacts/contracts/Arbiter.json diff --git a/src/Artifacts/contracts/Bondage.json b/src/artifacts/contracts/Bondage.json similarity index 100% rename from src/Artifacts/contracts/Bondage.json rename to src/artifacts/contracts/Bondage.json diff --git a/src/Artifacts/contracts/Client1.json b/src/artifacts/contracts/Client1.json similarity index 100% rename from src/Artifacts/contracts/Client1.json rename to src/artifacts/contracts/Client1.json diff --git a/src/Artifacts/contracts/Client2.json b/src/artifacts/contracts/Client2.json similarity index 100% rename from src/Artifacts/contracts/Client2.json rename to src/artifacts/contracts/Client2.json diff --git a/src/Artifacts/contracts/Client3.json b/src/artifacts/contracts/Client3.json similarity index 100% rename from src/Artifacts/contracts/Client3.json rename to src/artifacts/contracts/Client3.json diff --git a/src/Artifacts/contracts/Client4.json b/src/artifacts/contracts/Client4.json similarity index 100% rename from src/Artifacts/contracts/Client4.json rename to src/artifacts/contracts/Client4.json diff --git a/src/Artifacts/contracts/ClientBytes32Array.json b/src/artifacts/contracts/ClientBytes32Array.json similarity index 100% rename from src/Artifacts/contracts/ClientBytes32Array.json rename to src/artifacts/contracts/ClientBytes32Array.json diff --git a/src/Artifacts/contracts/ClientIntArray.json b/src/artifacts/contracts/ClientIntArray.json similarity index 100% rename from src/Artifacts/contracts/ClientIntArray.json rename to src/artifacts/contracts/ClientIntArray.json diff --git a/src/Artifacts/contracts/CurrentCost.json b/src/artifacts/contracts/CurrentCost.json similarity index 100% rename from src/Artifacts/contracts/CurrentCost.json rename to src/artifacts/contracts/CurrentCost.json diff --git a/src/Artifacts/contracts/Dispatch.json b/src/artifacts/contracts/Dispatch.json similarity index 100% rename from src/Artifacts/contracts/Dispatch.json rename to src/artifacts/contracts/Dispatch.json diff --git a/src/Artifacts/contracts/Piecewise.json b/src/artifacts/contracts/Piecewise.json similarity index 100% rename from src/Artifacts/contracts/Piecewise.json rename to src/artifacts/contracts/Piecewise.json diff --git a/src/Artifacts/contracts/Registry.json b/src/artifacts/contracts/Registry.json similarity index 100% rename from src/Artifacts/contracts/Registry.json rename to src/artifacts/contracts/Registry.json diff --git a/src/Artifacts/contracts/RegistryInterface.json b/src/artifacts/contracts/RegistryInterface.json similarity index 100% rename from src/Artifacts/contracts/RegistryInterface.json rename to src/artifacts/contracts/RegistryInterface.json diff --git a/src/Artifacts/contracts/TokenDotFactory.json b/src/artifacts/contracts/TokenDotFactory.json similarity index 100% rename from src/Artifacts/contracts/TokenDotFactory.json rename to src/artifacts/contracts/TokenDotFactory.json diff --git a/src/Artifacts/contracts/TokenFactory.json b/src/artifacts/contracts/TokenFactory.json similarity index 100% rename from src/Artifacts/contracts/TokenFactory.json rename to src/artifacts/contracts/TokenFactory.json diff --git a/src/Artifacts/contracts/ZapCoordinator.json b/src/artifacts/contracts/ZapCoordinator.json similarity index 100% rename from src/Artifacts/contracts/ZapCoordinator.json rename to src/artifacts/contracts/ZapCoordinator.json diff --git a/src/Artifacts/contracts/ZapCoordinatorInterface.json b/src/artifacts/contracts/ZapCoordinatorInterface.json similarity index 100% rename from src/Artifacts/contracts/ZapCoordinatorInterface.json rename to src/artifacts/contracts/ZapCoordinatorInterface.json diff --git a/src/Artifacts/contracts/ZapToken.json b/src/artifacts/contracts/ZapToken.json similarity index 100% rename from src/Artifacts/contracts/ZapToken.json rename to src/artifacts/contracts/ZapToken.json diff --git a/src/Artifacts/src/tests/__init__.py b/src/artifacts/src/__init__.py similarity index 100% rename from src/Artifacts/src/tests/__init__.py rename to src/artifacts/src/__init__.py diff --git a/src/Artifacts/src/index.py b/src/artifacts/src/index.py similarity index 100% rename from src/Artifacts/src/index.py rename to src/artifacts/src/index.py diff --git a/src/base_contract/__init__.py b/src/base_contract/__init__.py new file mode 100644 index 00000000..c54d3f94 --- /dev/null +++ b/src/base_contract/__init__.py @@ -0,0 +1,2 @@ +from web3 import Web3 +from src.artifacts.src.index import Artifacts \ No newline at end of file diff --git a/src/base_contract/base_contract.py b/src/base_contract/base_contract.py new file mode 100644 index 00000000..07dbb55a --- /dev/null +++ b/src/base_contract/base_contract.py @@ -0,0 +1,102 @@ +from web3 import Web3 +import asyncio + +import src.artifacts.src.index as index +import src.base_contract.utils as utils + + +class BaseContract: + provider: any + web3: any + contract: any + network_id: int + coordinator: any + artifact: any + name: str + address: str or None + + def __init__(self, + artifact_name: str, + web3: any = None, + network_id: int = 1, + network_provider: any = None, + artifacts_dir: str = None, + coordinator: str = None, + address: str = None + ): + try: + if artifacts_dir is None: + self.artifact = index.Artifacts[artifact_name] + self.coor_artifact = index.Artifacts['ZAPCOORDINATOR'] + else: + artifacts: any = utils.Utils.get_artifacts(artifacts_dir) + self.artifact = utils.Utils.open_artifact_in_dir(artifacts[artifact_name]) + self.coor_artifact = utils.Utils.open_artifact_in_dir(artifacts['ZAPCOORDINATOR']) + + self.name = artifact_name + self.provider = web3 or Web3(network_provider or Web3.HTTPProvider("https://cloudflare-eth.com")) + self.w3 = web3 or Web3(network_provider or Web3.HTTPProvider("https://cloudflare-eth.com")) + self.network_id = network_id or 1 + self.address = address or self.artifact['networks'][str(self.network_id)]['address'] + + if coordinator is not None: + self.coordinator = self.w3.eth.contract(address=self.w3.toChecksumAddress(coordinator), + abi=self.coor_artifact['abi']) + + contract_address = self.get_contract() + self.contract = self.w3.eth.contract(address=self.w3.toChecksumAddress(contract_address), + abi=self.artifact['abi']) + + else: + self.coor_address = self.coor_artifact['networks'][str(self.network_id)]['address'] + self.coordinator = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.coor_address), + abi=self.coor_artifact['abi']) + + self.contract = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.address), + abi=self.artifact['abi']) + + except Exception as e: + raise e + + async def _get_contract(self) -> str: + """ + This async function fetches the contract address from the coordinator abi. Thereafter, the function returns the + address where it is assigned to the address kwarg in the contract instance. + + :return: the contract address of the coordinator. + """ + await asyncio.sleep(.5) + contract_address = self.coordinator.functions.getContract(self.name).call() + return contract_address + + async def _get_contract_owner(self) -> str: + """ + This async function fetches the owner of the contract instance. + + :return: the contract owner's address. + """ + await asyncio.sleep(.5) + contract_owner = self.contract.functions.owner().call() + return contract_owner + + def get_contract(self) -> str: + """ + A synchronous function that wraps the asynchronous _get_contract method. This function returns the contract + address fetched from the asynchronous _get_contract function. + + :return: the contract address. + """ + task = self._get_contract() + contract_address = asyncio.run(task) + return contract_address + + def get_contract_owner(self) -> str: + """ + A synchronous function that wraps the asynchronous _get_contract_owner method. This function returns the + contract owner's address. + + :return: the contract owner's address. + """ + task = self._get_contract_owner() + owner = asyncio.run(task) + return owner diff --git a/src/base_contract/utils.py b/src/base_contract/utils.py new file mode 100644 index 00000000..45ef1ec7 --- /dev/null +++ b/src/base_contract/utils.py @@ -0,0 +1,41 @@ +import os +import json + + +class Utils: + + """ + # @ignore + # @params {string} buildDir + # @returns {any} + """ + + @staticmethod + def get_artifacts(build_dir: str) -> dict: + artifacts = { + 'ARBITER': os.path.join(build_dir, 'Arbiter.json'), + 'BONDAGE': os.path.join(build_dir, 'Bondage.json'), + 'DISPATCH': os.path.join(build_dir, 'Dispatch.json'), + 'REGISTRY': os.path.join(build_dir, 'Registry.json'), + 'CurrentCost': os.path.join(build_dir, 'CurrentCost.json'), + 'PiecewiseLogic': os.path.join(build_dir, 'PiecewiseLogic.json'), + 'ZAP_TOKEN': os.path.join(build_dir, 'ZapToken.json'), + 'Client1': os.path.join(build_dir, 'Client1.json'), + 'Client2': os.path.join(build_dir, 'Client2.json'), + 'Client3': os.path.join(build_dir, 'Client3.json'), + 'Client4': os.path.join(build_dir, 'Client4.json'), + 'ZAPCOORDINATOR': os.path.join(build_dir, 'ZapCoordinator.json'), + 'TOKENDOTFACTORY': os.path.join(build_dir, 'TokenDotFactory.json'), + } + return artifacts + + """ + # @ignore + # @params {string} artifact + # @returns {dict} + """ + @staticmethod + def open_artifact_in_dir(artifact: str) -> dict: + with open(artifact) as f: + abi = json.load(f) + return abi diff --git a/src/BaseContract/test/__init__.py b/src/dispatch/__init__.py similarity index 100% rename from src/BaseContract/test/__init__.py rename to src/dispatch/__init__.py diff --git a/src/dispatch/dispatch.py b/src/dispatch/dispatch.py new file mode 100644 index 00000000..6e4acc4d --- /dev/null +++ b/src/dispatch/dispatch.py @@ -0,0 +1,307 @@ +from typing import Optional +from web3 import Web3 +import asyncio + +from src.base_contract.base_contract import BaseContract +from src.types.types import ( + Filter, address, TransactionCallback, + NetworkProviderOptions, const, txid +) + + +class ZapDispatch(BaseContract): + """ + The ZapDispatch class provides an interface to the Dispatch contract which enables data queries and responses + between data providers (oracles) and subscribers. This child class inherits the properties and methods from the + parent BaseContract class. + + :param NetworkProviderOptions: Dictionary object containing options for BaseContract init + :param network_id: Select which network the contract is located + options : (mainnet, testnet, private) + :param network_provider: Ethereum network provider (e.g. Infura or web3) + Example: + ZapDispatch({"network_id": 42, "network_provider" : 'web3'}) + """ + def __init__(self, options: NetworkProviderOptions = {}): + options["artifact_name"] = "DISPATCH" + super().__init__(**options) + + ###### Methods ###### + + async def query_data(self, + provider: address, + query: str, + endpoint: str, + endpoint_params: list[str], + from_address: address, + gas: Optional[int] = const.DEFAULT_GAS, + gas_price: Optional[int] = Web3().eth.gas_price) -> txid: + """ + Queries data from a subscriber to a given provider's endpoint. This function passes in both a query string and + a list of endpoint parameters that will be processed by the oracle. + + :param provider: Address of the data provider (oracle). + :param query: Data requested from the subscriber. + :param endpoint: Data endpoint of the data provider which is meant to determine how the query is handled. + :param endpoint_params: Parameters passed to the data provider's endpoint. + :param from_address: Address of the subscriber. + :param gas_price: Price per unit of gas (optional). + :param gas: The gas limit of this transaction (optional). + """ + if len(endpoint_params) > 0: + hex_params = [param if param.find('0x') == 0 else Web3.toHex(text=param) for param in endpoint_params] + bytes_params = [Web3.toBytes(hexstr=hex_p) for hex_p in hex_params] + endpoint_params = bytes_params + + try: + await asyncio.sleep(.5) + tx: txid = self.contract.functions.query( + provider, query, Web3.toBytes(text=endpoint), endpoint_params + ).transact( + {'from': from_address, 'gas': gas, 'gasPrice': gas_price} + ) + + tx_hash = Web3.toHex(tx) + return tx_hash + + except Exception as e: + print(e) + + async def cancel_query(self, + query_id: str or int, + from_address: address, + gas_price: Optional[int] = Web3().eth.gas_price, + gas: Optional[int] = const.DEFAULT_GAS) -> str or int: + """ + This function cancels a query_id. It will return the block number when the query was canceled. If the query + does not exist, a value error exception wil occur and the returned value will be zero. + + :param query_id: A unique identifier for the query. + :param from_address: Address of the subscriber. + :param gas_price: Price per unit of gas (optional). + :param gas: The gas limit of this transaction (optional). + """ + try: + await asyncio.sleep(.5) + self.contract.functions.cancelQuery(query_id).transact( + {'from': from_address, 'gas': gas, 'gasPrice': gas_price} + ) + except ValueError: + return 0 + + else: + return self.contract.functions.getCancel(query_id).call() + + async def respond(self, + query_id: str or int, + response_params: list[str], + dynamic: bool, + from_address: address, + gas_price: Optional[int] = Web3().eth.gas_price, + gas: Optional[int] = const.DEFAULT_GAS) -> txid: + """ + This function allows a provider to respond to a subscriber's query. The length and content of the response + parameters determines the type of response sent back to the subscriber. + + :param query_id: A unique identifier for the query. + :param response_params: List of responses returned by provider. Length determines the Dispatch response. + :param dynamic: Determines if the IntArray/Bytes32Array Dispatch response should be used. + :param from_address: Address of the subscriber. + :param gas_price: Price per unit of gas (optional). + :param gas: The gas limit of this transaction (optional). + """ + if dynamic is not False: + str_params = [str(param) for param in response_params] + hex_params = [param.encode('utf-8').hex() for param in str_params] + + if type(response_params[0]) == int: + int_params = [int(param) for param in hex_params] + response_params = int_params + + ### Omitted conversion to locale string ### + + tx = self.contract.functions.respondIntArray( + query_id, response_params + ).transact( + {'from': from_address, 'gas': gas, 'gasPrice': gas_price} + ) + + await asyncio.sleep(.5) + tx_hash = Web3.toHex(tx) + return tx_hash + + else: + response_params = hex_params + + tx = self.contract.functions.respondBytes32Array( + query_id, response_params + ).transact( + {'from': from_address, 'gas': gas, 'gasPrice': gas_price} + ) + await asyncio.sleep(.5) + tx_hash = Web3.toHex(tx) + return tx_hash + + p_length = len(response_params) + + if p_length == 1: + tx = self.contract.functions.respond1( + query_id, response_params[0] + ).transact( + {'from': from_address, 'gas': gas, 'gasPrice': gas_price} + ) + + elif p_length == 2: + tx = self.contract.functions.respond2( + query_id, response_params[0], response_params[1] + ).transact( + {'from': from_address, 'gas': gas} + ) + + elif p_length == 3: + tx = self.contract.functions.respond3( + query_id, response_params[0], response_params[1], response_params[2] + ).transact( + {'from': from_address, 'gas': gas, 'gasPrice': gas_price} + ) + + elif p_length == 4: + tx = self.contract.functions.respond4( + query_id, response_params[0], response_params[1], response_params[2], response_params[3] + ).transact( + {'from': from_address, 'gas': gas, 'gasPrice': gas_price} + ) + + else: + raise ValueError('Invalid number of response parameters') + + tx_hash = Web3.toHex(tx) + return tx_hash + + ###### Getters ###### + + async def get_query_id_provider(self, query_id: str or int) -> address: + """ + Fetches the provider of specified query id. + + :param query_id: A unique identifier for the query. + """ + await asyncio.sleep(.1) + return self.contract.functions.getProvider(query_id).call() + + async def get_subscriber(self, query_id: str or int) -> address: + """ + Fetches the subscriber's address that submitted the query associated with the query id. + + :param query_id: A unique identifier for the query. + """ + await asyncio.sleep(.1) + return self.contract.functions.getSubscriber(query_id).call() + + async def get_endpoint(self, query_id: str or int) -> str: + """ + Fetches the endpoint of the query. + + :param query_id: A unique identifier for the query. + """ + await asyncio.sleep(.1) + endpoint = self.contract.functions.getEndpoint(query_id).call() + return Web3.toText(endpoint) + + async def get_status(self, query_id: str or int) -> int: + """ + Fetches the status of a query id. + + :param query_id: A unique identifier for the query. + """ + await asyncio.sleep(.1) + return self.contract.functions.getStatus(query_id).call() + + async def get_cancel(self, query_id: str or int) -> int: + """ + Fetches the status of a cancelled query. + + :param query_id: A unique identifier for the query. + """ + await asyncio.sleep(.1) + return self.contract.functions.getCancel(query_id).call() + + async def get_user_query(self, query_id: str or int) -> str: + """ + Fetches the user of the specified query id. + + :param query_id: A unique identifier for the query. + """ + await asyncio.sleep(.1) + return self.contract.functions.getUserQuery(query_id).call() + + async def get_subscriber_onchain(self, query_id: str or int) -> bool: + """ + Fetches information about onchain or offchain subscriber of the specified query id. + + :param query_id: A unique identifier for the query. + """ + await asyncio.sleep(.1) + return self.contract.functions.getSubscriberOnchain(query_id).call() + + ###### Events ###### + + def listen(self, filters: Filter = {}, cb: TransactionCallback = None, ) -> None: + """ + Listens for all Dispatch contract events based on the optional filter. + + :param filters: subscriber:address, provider:address, id:int|string, endpoint:bytes32, + endpointParams:bytes32[], onchainSubscriber:boolean. + :param cb: Callback. + """ + + self.contract.events.allEvents( + filters, {'fromBlock': filters['fromBlock'] or 0, 'toBlock': 'latest'}, cb + ) + + async def listen_incoming(self, filters: Filter = {}, cb: TransactionCallback = None) -> None: + """ + Listens for "Incoming" Dispatch contract events based on an optional filter. This event listener executes + a callback when the filter is matched. + + :param filters: subscriber:address, provider:address, endpoint:bytes32. + :param cb: Callback. + """ + + self.contract.events.Incoming(filters, cb) + + async def listen_fulfill_query(self, filters: Filter = {}, cb: TransactionCallback = None) -> None: + """ + Listens for "FulfillQuery" Dispatch contract events based on an optional filter. + + :param filters: subscriber:address, provider:address, endpoint:bytes32. + :param cb: Callback. + """ + + self.contract.events.FulfillQuery(filters, cb) + + async def listen_offchain_response(self, filters: Filter = {}, cb: TransactionCallback = None) -> None: + """ + Listens for all Offchain responses Dispatch contract events based on an optional filter. + + :param filters: id:number|string, subscriber:address, provider: address, response: bytes32[]|int[], + response1:string, response2:string, response3:string, response4:string. + :param cb: Callback. + """ + + self.contract.events.OffchainResponse(filters, cb) + self.contract.events.OffchainResponseInt(filters, cb) + self.contract.events.OffchainResult1(filters, cb) + self.contract.events.OffchainResult2(filters, cb) + self.contract.events.OffchainResult3(filters, cb) + self.contract.events.OffchainResult4(filters, cb) + + async def listen_cancel_request(self, filters: Filter = {}, cb: TransactionCallback = None) -> None: + """ + Listens for "Cancel" query events. + + :param filters: + :param cb: Callback. + """ + + self.contract.events.CanceledRequest(filters, cb) diff --git a/src/portedFiles/default_tx_test.py b/src/portedFiles/default_tx_test.py deleted file mode 100644 index 0aa58140..00000000 --- a/src/portedFiles/default_tx_test.py +++ /dev/null @@ -1,9 +0,0 @@ -from default_tx import DefaultTx -import unittest - -class DefaultTxTest(unittest.TestCase): - def test_instance(self): - defaultTx = DefaultTx("5555555555", 5, 51416566) - self.assertEqual(defaultTx.address, "5555555555") - self.assertEqual(defaultTx.gas, 5) - self.assertEqual(defaultTx.gasPrice, 51416566) diff --git a/src/portedFiles/filter_test.py b/src/portedFiles/filter_test.py deleted file mode 100644 index abdd15a6..00000000 --- a/src/portedFiles/filter_test.py +++ /dev/null @@ -1,13 +0,0 @@ -from filter import Filter -import unittest - -class TestFilter(unittest.TestCase): - def test_instance(self): - filterTest = Filter(11, 22, "123123123", "321321321", "221221221", "11111", 123) - self.assertEqual(filterTest.fromBlock, 11) - self.assertEqual(filterTest.toBlock, 22) - self.assertEqual(filterTest.provider, "123123123") - self.assertEqual(filterTest.subscriber, "321321321") - self.assertEqual(filterTest.terminator, "221221221") - self.assertEqual(filterTest.endpoint, "11111") - self.assertEqual(filterTest.id, 123) diff --git a/tests/base_contract/__init__.py b/tests/base_contract/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/base_contract/conftest.py b/tests/base_contract/conftest.py new file mode 100644 index 00000000..2a0adcc5 --- /dev/null +++ b/tests/base_contract/conftest.py @@ -0,0 +1,37 @@ +from unittest.mock import MagicMock, patch +import pytest +import re +import src.base_contract.base_contract as base_contract + +"""Default setup""" + +MOCK_ABI = { + 'TEST_ARTIFACT': {'abi': [], 'networks': + {'1': {'address': '0xmainnet'}, + '42': {'address': '0xkovan'}, + '31337': {'address': '0xdevnet'}}}, + 'ZAPCOORDINATOR': {'abi': [], 'networks': + {'1': {'address': '0xcoormainnet'}, + '42': {'address': '0xcoorkovan'}, + '31337': {'address': '0xcoordevnet'}}} +} + + +@pytest.fixture +def anyio_backend(): + """ + Ensures anyio uses the default, pytest-asyncio plugin + for running async tests. + """ + return 'asyncio' + + +@pytest.fixture +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=True) +def instance(mock_Web3): + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + return base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) diff --git a/tests/base_contract/test_base_contract.py b/tests/base_contract/test_base_contract.py new file mode 100644 index 00000000..9712f451 --- /dev/null +++ b/tests/base_contract/test_base_contract.py @@ -0,0 +1,477 @@ +""" + This module tests the different possible branches of execution within the Zap Base Contract. The following unit + tests require minimal dependencies. + + The Base Contract is the parent class to the Dispatch, Bondage, Arbiter, Token, and Registry classes; further, it + provides access to both the contract instance as well as the Web3 provider instance. Because the majority of this + contract is a constructor, the focus here lies in testing the instantiated dynamic attributes of the base contract + class. +""" + +from unittest.mock import MagicMock, patch +import pytest +import re +import src.base_contract.base_contract as base_contract + + +"""Default setup""" + + +MOCK_ABI = { + 'TEST_ARTIFACT': {'abi': [], 'networks': + {'1': {'address': '0xmainnet'}, + '42': {'address': '0xkovan'}, + '31337': {'address': '0xdevnet'}}}, + 'ZAPCOORDINATOR': {'abi': [], 'networks': + {'1': {'address': '0xcoormainnet'}, + '42': {'address': '0xcoorkovan'}, + '31337': {'address': '0xcoordevnet'}}} +} + + +def capture_args(*args, **kwargs) -> any: + """Side effect function for checking args and kwargs passed""" + return args, kwargs + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=True) +class TestInit: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + + def test_instance_name(self, mock_Web3, instance): + """ + Sanity check to ensure the instance runs without errors while mocking web3. + """ + assert type(instance.name) == str + assert instance.name == 'TEST_ARTIFACT' + + with pytest.raises(AssertionError): + assert type(instance.name) != str + assert instance.name != 'TEST_ARTIFACT' + + def test_name_without_arg(self, mock_Web3): + """ + Testing that the contract fails if the artifact_name kwarg is an empty string. + """ + with pytest.raises(KeyError): + instance = base_contract.BaseContract(artifact_name='', web3=self.w3) + assert instance + + def test_network_id_default(self, mock_Web3, instance): + """ + Testing that the default network (1) is assigned to the contract instance. + """ + assert type(instance.network_id) == int + assert instance.network_id == 1 + + with pytest.raises(AssertionError): + assert type(instance.network_id) != int + assert instance.network_id != 1 + + @pytest.mark.parametrize('input', [1, 42, 31337]) + def test_assigned_network_ids(self, mock_Web3, input): + """ + Testing that the network kwarg gets assigned to the contract network instance. + + :param mock_Web3: patched web3. + :param input: the relevant networks' corresponding integer. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input, web3=self.w3) + + assert instance.network_id == input + + with pytest.raises(AssertionError): + assert instance.network_id != input + + @pytest.mark.parametrize('wrong_net_id', [11, 16, 47, 118893, 'string']) + def test_network_id_should_fail_if_given_unknown_id(self, mock_Web3, wrong_net_id): + """ + Testing that the contract fails if given an unknown network id. + """ + with pytest.raises(KeyError): + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=wrong_net_id, web3=self.w3) + assert instance + + def test_network_id_of_zero(self, mock_Web3): + """ + Testing that the network_id of zero actually uses the default network of '1.' + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=0, web3=self.w3) + + assert instance.network_id != 0 + assert instance.network_id == 1 + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=False) +class TestAddress: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + + def test_address_argument(self, mock_Web3): + """ + Testing that the address argument is assigned as the instance address. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', address='0x_some_address', web3=self.w3) + + assert type(instance.address) == str + assert instance.address == '0x_some_address' + + with pytest.raises(AssertionError): + assert instance.address == '0x_some_other_address' + + @pytest.mark.parametrize('network_input, address_output', [(1, '0xmainnet'), (42, '0xkovan'), (31337, '0xdevnet')]) + def test_address_with_no_argument(self, mock_Web3, network_input, address_output): + """ + Testing that the address is correctly fetched from the abi when no address arg is given. + + :param mock_Web3: patched web3. + :param network_input: relevant networks including mainnet, kovan, and devnet. + :param address_output: the associated address within the MOCK_ABI dictionary. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=network_input, web3=self.w3) + + assert type(instance.address) == str + assert instance.address == address_output + + with pytest.raises(AssertionError): + assert instance.address == '0x_wrong_address' + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=True) +class TestArtifacts: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + + def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3, instance): + """ + Testing that the artifact_name kwarg triggers the artifacts dictionary and populates the artifact + attribute with the controlled mock abi. + + :param mock_Web3: patched web3. + """ + assert type(instance.artifact) == dict + assert instance.artifact['networks']['1']['address'] == '0xmainnet' + + assert type(instance.coor_artifact) == dict + assert instance.coor_artifact['networks']['1']['address'] == '0xcoormainnet' + + with pytest.raises(AssertionError): + assert type(instance.artifact) != dict + assert instance.artifact['networks']['1']['address'] == '0x123' + + assert type(instance.coor_artifact) != dict + assert instance.coor_artifact['networks']['1']['address'] == '0x123' + + +@patch('src.base_contract.Web3', autospec=False) +class TestArtifactsDirectory: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + + mock_test_abi = {'abi': [], 'networks': {'1': {'address': '0xmainnet'}, '42': {'address': '0xkovan'}, + '31337': {'address': '0xdevnet'}}} + + mock_coor_abi = {'abi': [], 'networks': {'1': {'address': '0xcoormainnet'}, '42': {'address': '0xcoorkovan'}, + '31337': {'address': '0xcoordevnet'}}} + + mock_dict_dir = {'TEST_ARTIFACT': 'artifacts/contracts/TestArtifact.json', + 'ZAPCOORDINATOR': 'artifacts/contracts/ZapCoordinator.json'} + + @patch('src.base_contract.utils.Utils.get_artifacts') + @patch('src.base_contract.utils.Utils.open_artifact_in_dir') + @pytest.mark.parametrize( + 'art_input, net_id_input, address_output, coor_address_output', [ + ('TEST_ARTIFACT', 1, '0xmainnet', '0xcoormainnet'), ('TEST_ARTIFACT', 42, '0xkovan', '0xcoorkovan'), + ('TEST_ARTIFACT', 31337, '0xdevnet', '0xcoordevnet') + ]) + def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mock_artifact_dir, mock_Web3, + art_input, net_id_input, address_output, + coor_address_output): + """ + Testing that the artifacts_dir kwarg passes through both get_artifacts and open_artifact_in_dir functions + respectively. Thereafter, the base_contract instance attribute 'artifact' should be assigned as an object + (abi). The get_artifacts and open_artifact_in_dir functions are patched and return expected values. + + :param mock_utils_abi: a mocked abi that mimics what open_artifacts_in_dir function will return. + :param mock_artifact_dir: a small mocked directory that replicates the return of get_artifacts function. + :param mock_Web3: patched web3. + """ + + """Mock the relevant function returns""" + mock_artifact_dir.return_value = self.mock_dict_dir + mock_utils_abi.side_effect = [self.mock_test_abi, self.mock_coor_abi] + + instance = base_contract.BaseContract(artifact_name=art_input, artifacts_dir='some/path/', + network_id=net_id_input, web3=self.w3) + + assert type(instance.artifact) == dict + assert type(instance.artifact['abi']) == list + assert instance.artifact['networks'][str(net_id_input)]['address'] == address_output + + """Checking coordinator instance object and coordinator instance address are also set""" + + assert type(instance.coor_address) == str + assert type(instance.coor_artifact['abi']) == list + assert instance.coor_address == coor_address_output + + """Assertions regarding the artifact instance that should fail""" + + with pytest.raises(AssertionError): + assert type(instance.artifact) == list + assert type(instance.artifact['abi']) == dict + assert instance.artifact['network'][str(net_id_input)]['address'] == '0xfailingstring' + assert instance.artifact['abi']['network'] + + """Assertions regarding the coordinator artifact instance that should fail""" + + assert type(instance.coor_address) != str + assert type(instance.coor_artifact['abi']) != dict + assert instance.coor_address == '0xfailingstring' + assert instance.coor_artifact['abi']['network'] + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=False) +class TestContracts: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.toChecksumAddress.side_effect = capture_args + w3.eth.contract.side_effect = capture_args + + @patch('src.base_contract.base_contract.BaseContract.get_contract') + @pytest.mark.parametrize('net_id', [1, 42, 31337]) + def test_coordinator_contract_args_with_coordinator_address_provided(self, mock_get_contract, mock_Web3, net_id): + """ + Testing that the coordinator contract object is assigned with the correct args. This test first mocks the + get_contract function so the contract instance runs without errors. + """ + mock_get_contract = MagicMock() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x_some_address', + network_id=net_id, web3=self.w3) + + assert type(instance.coordinator) == tuple + assert instance.coordinator[1]['abi'] == [] + + """ + Iterate through the returned tuple to find the passed address. Because of all the mocks, the returned + kwargs include a lot of unnecessary punctuation--hence the use of regexp. + """ + + expected_address = '0x_some_address' + + res = re.search(expected_address, str(instance.coordinator[1]['address'])) + assert res is not None + + with pytest.raises(AssertionError): + res = re.search('this_should_fail', str(instance.coordinator[1]['address'])) + assert res is not None + + @pytest.mark.parametrize('input_id, expected_output', [(1, '0xcoormainnet'), (42, '0xcoorkovan'), + (31337, '0xcoordevnet')]) + def test_coordinator_contract_without_coordinator_address_provided(self, mock_Web3, input_id, expected_output): + """ + Testing that the coordinator contract object is assigned with the correct args. The first assertions test that + the coordinator instance was correctly assigned. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id, web3=self.w3) + + assert type(instance.coordinator) == tuple + assert instance.coordinator[1]['abi'] == [] + + res = re.search(expected_output, str(instance.coordinator[1]['address'])) + assert res is not None + + with pytest.raises(AssertionError): + res = re.search('this_should_fail', str(instance.coordinator[1]['address'])) + assert res is not None + + @patch('src.base_contract.base_contract.BaseContract.get_contract') + def test_contract_instance_with_coor_through_get_contract_method(self, mock_get_contract, mock_Web3): + """ + Testing that the return value from get_contract passes through to the contract instance object. This test + mocks the get_contract function to return the expected value. + """ + test_address = '0x_some_address' + mock_get_contract.return_value = test_address + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator=test_address, web3=self.w3) + + res = re.search(test_address, str(instance.contract[1]['address'])) + assert res is not None + + @pytest.mark.parametrize('input_id, expected_address', [(1, '0xmainnet'), (42, '0xkovan'), (31337, '0xdevnet')]) + def test_contract_instance_if_no_coor_provided(self, mock_Web3, input_id, expected_address): + """ + Testing that the contract instance object is assigned with the correct args using the MOCK_ABI with + different networks. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id, web3=self.w3) + + assert type(instance.contract) == tuple + assert instance.contract[1]['abi'] == [] + + expected_address = expected_address + res = re.search(expected_address, str(instance.contract[1]['address'])) + assert res is not None + + with pytest.raises(AssertionError): + res = re.search('this_will_fail', str(instance.contract[1]['address'])) + assert res is not None + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=False) +class TestWrapperMethods: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.toChecksumAddress.side_effect = capture_args + w3.eth.contract.side_effect = capture_args + + @patch('src.base_contract.base_contract.BaseContract._get_contract') + def test_sync_get_contract(self, mock_async_get_contract, mock_Web3): + """ + Testing that the get_contract function returns the proper values from the _get_contract async function. This + test mocks the async _get_contract function and gives it a return value to check that the get_contract + wrapper returns the proper value. + """ + mock_async_get_contract.return_value = 'test_get_contract_address' + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='some_address', web3=self.w3) + + assert type(instance.get_contract()) == str + assert instance.get_contract() == 'test_get_contract_address' + + """Test false positive""" + + with pytest.raises(AssertionError): + assert instance.get_contract() == 'this_should_fail' + + + @patch('src.base_contract.base_contract.BaseContract._get_contract_owner') + def test_sync_get_contract_owner(self, mock_async_owner, mock_Web3, instance): + """ + Testing that the get_contract_owner returns the proper values from the async _get_contract_owner function. + The test mocks the async _get_contract_owner function and gives it a return value to check that the + get_contract_owner wrapper returns the proper value. + """ + mock_async_owner.return_value = 'test_owner_address' + + assert type(instance.get_contract_owner()) == str + assert instance.get_contract_owner() == 'test_owner_address' + + with pytest.raises(AssertionError): + assert instance.get_contract_owner() == 'this_should_fail' + + +class TestAsyncMethods: + + """ + The following asynchronous functions should not be called directly; rather, the wrapper function should be + called instead. The following unit tests require the somewhat hacky functions mock_owner_abi and mock_coor_abi + in order to ensure the async functions were called. The mock abi functions are returned as a side effect from + the web3.eth.contract call. While patching both Web3 and the artifacts dictionary, these functions proved + difficult to test (hence, the creative workaround). The Pythonic syntax of these side effects are, nevertheless, + exactly the same: + _get_contract_owner() = .functions.owner().call() + _get_contract() = .functions.getContract(self.name).call() + """ + + """Setup""" + + + def mock_owner_abi(self, *args, **kwargs): + """Function mimicking abi""" + return_val = '0x_owner_address' + class mimicked_abi: + class functions: + class owner: + def call(self): + return return_val + return mimicked_abi + + + def mock_coor_abi(self, *args, **kwargs): + """Function mimicking abi""" + return_val = '0x_artifact_address' + + class mimicked_coordinator: + class functions: + class getContract: + def __init__(self, name): + self.name = name + def call(self): + return return_val + return mimicked_coordinator + + @pytest.mark.anyio + async def test_async_get_contract_owner(self): + """ + Testing the asynchronous _get_contract_owner function. + """ + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = self.mock_owner_abi + w3.toChecksumAddress.side_effect = MagicMock() + + with patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True): + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) + res = await instance._get_contract_owner() + assert res == '0x_owner_address' + + with pytest.raises(AssertionError): + assert res == 'this_should_fail' + + @pytest.mark.anyio + async def test_async_get_contract(self): + """ + Testing the asynchronous _get_contract function. + """ + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = self.mock_coor_abi + w3.toChecksumAddress.side_effect = MagicMock() + + with patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True): + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) + res = await instance._get_contract() + assert res == '0x_artifact_address' + + with pytest.raises(AssertionError): + assert res == 'this_should_fail' diff --git a/tests/dispatch/__init__.py b/tests/dispatch/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/dispatch/conftest.py b/tests/dispatch/conftest.py new file mode 100644 index 00000000..3355a1a1 --- /dev/null +++ b/tests/dispatch/conftest.py @@ -0,0 +1,132 @@ +from pytest import fixture + +from web3 import Web3 + +from src.artifacts.src.index import Artifacts +from src.types.types import const + +"""Setup""" + +_w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545")) +_abi = {'dispatch': Artifacts['DISPATCH'], + 'registry': Artifacts['REGISTRY'], + 'bondage': Artifacts['BONDAGE'], + 'zap_token': Artifacts['ZAP_TOKEN']} + + +@fixture(scope="module") +def w3(): + return _w3 + + +@fixture(scope="module") +def address(w3): + return {name: Web3.toChecksumAddress( + abi["networks"]["31337"]["address"]) + for name, abi in _abi.items()} + + +@fixture(scope="module") +def dispatch_contract(w3, address): + _dispatch_contract = w3.eth.contract( + address=address["dispatch"], + abi=_abi["dispatch"]["abi"]) + + return _dispatch_contract + + +@fixture(scope="class") +def token_contract(w3, address): + _token_contract = w3.eth.contract( + address=address["zap_token"], + abi=_abi["zap_token"]["abi"]) + + return _token_contract + + +@fixture(scope='class') +def bondage_contract(w3, address): + bond_contract = w3.eth.contract( + address=address['bondage'], + abi=_abi['bondage']['abi']) + return bond_contract + + +@fixture(scope='class') +def registry_contract(w3, address): + reg_contract = w3.eth.contract( + address=address['registry'], + abi=_abi['registry']['abi']) + return reg_contract + + +@fixture(scope="module") +def accounts(w3): + return w3.eth.accounts + + +@fixture(scope="module") +def owner(accounts): + return accounts[0] + + +@fixture(scope="module") +def subscriber(accounts): + return accounts[1] + + +@fixture(scope="module") +def oracle(accounts): + return accounts[8] + + +@fixture(scope="module") +def broker(accounts): + return accounts[3] + + +@fixture(scope="module") +def escrower(accounts): + return accounts[4] + + +@fixture(scope="module") +def provider(oracle): + prov_dict = { + "pubkey": 108, + "title": '0x4269616e6361', + "address": oracle, + "endpoint_params": ['param1', 'param2'], + "endpoint": 'Fibonacci', + "query": 'btcPrice', + "curve": [2, 5000, 2000, 1000, 2, 0, 3000, 10000], + "broker": '0x0000000000000000000000000000000000000000' + } + return prov_dict + + +@fixture(scope="module") +def anyio_backend(): + return 'asyncio' + + +@fixture(scope='module') +def meta_config(subscriber, w3): + config = { + 'from_address': subscriber, + 'gas': const.DEFAULT_GAS, + 'gas_price': w3.eth.gas_price + } + return config + + +@fixture(scope='module') +def query_config(oracle, provider, subscriber, w3, meta_config): + config = { + 'provider': oracle, + 'query': provider['query'], + 'endpoint': provider['endpoint'], + 'endpoint_params': provider['endpoint_params'], + **meta_config + } + return config diff --git a/tests/dispatch/test_dispatch.py b/tests/dispatch/test_dispatch.py new file mode 100644 index 00000000..207e628e --- /dev/null +++ b/tests/dispatch/test_dispatch.py @@ -0,0 +1,324 @@ +import pytest + +from web3 import Web3 + +from src.dispatch.dispatch import ZapDispatch +from src.types.types import const + +"""ZapDispatch contract setup""" + +web3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545')) +options = { + 'web3': web3, + 'network_id': 31337 +} +dispatch_wrapper = ZapDispatch(options) + + +class TestDispatch: + + def test_init(self, dispatch_contract, token_contract, bondage_contract, registry_contract, subscriber, + broker, oracle, provider, w3): + """ + Testing the dispatch, bondage, registry, zap_token initializations. + """ + assert dispatch_wrapper + assert type(dispatch_wrapper.address) == str + assert type(bondage_contract.address) == str + assert type(registry_contract.address) == str + assert type(token_contract.address) == str + assert type(broker) == str + assert broker == '0x90F79bf6EB2c4f870365E785982E1f101E93b906' + + def test_setup(self, oracle, broker, provider, subscriber, w3, accounts, bondage_contract, + registry_contract, token_contract, owner, dispatch_contract): + """ + Running and testing all prerequisites to ensure the correct allocation and approval of Zap. Further, this + function fetches the required Zap for dots from the Bondage contract, and finally, bonds the Zap for dots. + """ + + ep = 'endpoint'.encode('utf-8') + + for acct in accounts: + allocate = token_contract.functions.allocate(acct, 1000).transact( + {'from': owner, "gas": const.DEFAULT_GAS, "gasPrice": w3.eth.gas_price}) + assert allocate + + dots = 10 + required_zap = bondage_contract.functions.calcZapForDots(owner, ep, dots).call() + assert type(required_zap) == int + + approve_zap = token_contract.functions.approve(bondage_contract.address, required_zap).transact( + {'from': oracle, "gas": const.DEFAULT_GAS, "gasPrice": w3.eth.gas_price}) + assert approve_zap + + bonding = bondage_contract.functions.bond(owner, ep, dots).transact( + {'from': subscriber, "gas": const.DEFAULT_GAS, "gasPrice": w3.eth.gas_price}) + assert bonding + + @pytest.mark.asyncio + async def test_request_data(self, oracle, provider, subscriber, w3, dispatch_contract, query_config): + """ + Testing the request_data function. It should return a string. + """ + res = await dispatch_wrapper.query_data(**query_config) + assert type(res) == str + + with pytest.raises(AssertionError): + assert type(res) != str + + @pytest.mark.asyncio + async def test_cancel_query_with_error(self, subscriber, w3, meta_config): + """ + Testing the cancel function. Callback error should return a zero. + """ + config = { + 'query_id': 5 + } + + res = await dispatch_wrapper.cancel_query(**config, **meta_config) + assert type(res) == int + assert res == 0 + + @pytest.mark.asyncio + async def test_cancel_query_without_error(self, subscriber, w3, dispatch_contract, oracle, provider, + meta_config, query_config): + """ + Testing the cancel_query function. First, the query_id is captured from invoking the query_data function. Then, + the query_id is inserted as the id to cancel in the cancel_query function. It should return an integer of the + block from whence it was cancelled. + """ + + tx_hash = await dispatch_wrapper.query_data(**query_config) + tx_receipt = w3.eth.getTransactionReceipt(tx_hash) + query_id = dispatch_contract.events.Incoming( + ).processReceipt( + tx_receipt)[0]["args"]["id"] + + block_number = tx_receipt['blockNumber'] + assert query_id + + config_cancel = { + 'query_id': query_id, + } +# + res = await dispatch_wrapper.cancel_query(**config_cancel, **meta_config) + assert type(res) == int + assert res != 0 + assert res == block_number + 1 + + + @pytest.mark.asyncio + @pytest.mark.parametrize('failing_params', [[], ['res1', 'res2', 'res3', 'res4', 'res5', 'res6']]) + async def test_respond_dynamic_false_should_fail(self, failing_params, subscriber, w3, meta_config, + dispatch_contract, query_config, oracle): + """ + Testing the respond function. The response_params should cause the function to fail with a value error. + """ + tx_hash = await dispatch_wrapper.query_data(**query_config) + tx_receipt = w3.eth.getTransactionReceipt(tx_hash) + query_id = dispatch_contract.events.Incoming( + ).processReceipt( + tx_receipt)[0]["args"]["id"] + + assert query_id + + config = { + 'query_id': query_id, + 'response_params': failing_params, + 'dynamic': False, + 'from_address': oracle, + 'gas': const.DEFAULT_GAS, + 'gas_price': w3.eth.gas_price + } + + with pytest.raises(ValueError): + response = await dispatch_wrapper.respond(**config) + assert response + assert type(response) == str + + @pytest.mark.asyncio + @pytest.mark.parametrize('response_params', [['res1'], + ['res1', 'res2'], + ['res1', 'res2', 'res3'], + ['res1', 'res2', 'res3', 'res4']]) + async def test_respond_dynamic_false(self, dispatch_contract, query_config, w3, oracle, response_params): + """ + Testing that each set of parameters goes through without an error. + """ + tx_hash = await dispatch_wrapper.query_data(**query_config) + tx_receipt = w3.eth.getTransactionReceipt(tx_hash) + query_id = dispatch_contract.events.Incoming().processReceipt( + tx_receipt)[0]["args"]["id"] + + assert query_id + + config = { + 'query_id': query_id, + 'response_params': response_params, + 'dynamic': False, + 'from_address': oracle, + 'gas': const.DEFAULT_GAS, + 'gas_price': w3.eth.gas_price + } + + response = await dispatch_wrapper.respond(**config) + + assert response + assert type(response) == str + + @pytest.mark.asyncio + @pytest.mark.parametrize('input_res', [[1, 2], + [10293847566574839201, 1029384756574839201], + [1, 999999999999999999999], + [9999999, 8888888888888888, 3874, 1293847566, 5555555551]]) + async def test_response_dyanmic_int_array(self, dispatch_contract, query_config, w3, oracle, input_res): + """ + Testing that the respond function conditional runs as expected using varied combinations of integers. + """ + tx_hash = await dispatch_wrapper.query_data(**query_config) + tx_receipt = w3.eth.getTransactionReceipt(tx_hash) + query_id = dispatch_contract.events.Incoming().processReceipt( + tx_receipt)[0]["args"]["id"] + + assert query_id + + config = { + 'query_id': query_id, + 'response_params': input_res, + 'dynamic': True, + 'from_address': oracle, + 'gas': const.DEFAULT_GAS, + 'gas_price': w3.eth.gas_price + } + + response = await dispatch_wrapper.respond(**config) + assert response + + @pytest.mark.asyncio + @pytest.mark.parametrize('fail_res', [[-1, 2], + [1, 9.999999], + [9999999, 8888888888888888, 3874, 1293.847566, -5555555551]]) + async def test_response_dyanmic_int_array_should_fail(self, dispatch_contract, query_config, w3, oracle, fail_res): + """ + Testing that a value exception is raised for negative and floating-point numbers within the response parameters. + """ + tx_hash = await dispatch_wrapper.query_data(**query_config) + tx_receipt = w3.eth.getTransactionReceipt(tx_hash) + query_id = dispatch_contract.events.Incoming().processReceipt( + tx_receipt)[0]["args"]["id"] + + assert query_id + + config = { + 'query_id': query_id, + 'response_params': fail_res, + 'dynamic': True, + 'from_address': oracle, + 'gas': const.DEFAULT_GAS, + 'gas_price': w3.eth.gas_price + } + + with pytest.raises(ValueError): + response = await dispatch_wrapper.respond(**config) + assert response + + @pytest.mark.asyncio + async def test_bytes32_array(self, query_config, w3, dispatch_contract, oracle): + """ + Testing that the respond function conditional runs as expected. + """ + tx_hash = await dispatch_wrapper.query_data(**query_config) + tx_receipt = w3.eth.getTransactionReceipt(tx_hash) + query_id = dispatch_contract.events.Incoming().processReceipt( + tx_receipt)[0]["args"]["id"] + + assert query_id + + config = { + 'query_id': query_id, + 'response_params': ['p1', 'p2'], + 'dynamic': True, + 'from_address': oracle, + 'gas': const.DEFAULT_GAS, + 'gas_price': w3.eth.gas_price + } + + response = await dispatch_wrapper.respond(**config) + assert response + assert type(response) == str + + + +class TestGetters: + + @pytest.mark.asyncio + async def test_getters(self, provider, oracle, subscriber, w3, dispatch_contract, query_config): + """ + Testing the getter functions in one test, as they're fairly straightforward. + """ + tx_hash = await dispatch_wrapper.query_data(**query_config) + tx_receipt = w3.eth.getTransactionReceipt(tx_hash) + query_id = dispatch_contract.events.Incoming().processReceipt( + tx_receipt)[0]["args"]["id"] + + assert query_id + + """Test get_query_id_provider""" + + id_provider = await dispatch_wrapper.get_query_id_provider(query_id) + assert type(id_provider) == str + assert id_provider == '0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f' + + """Test get_subscriber""" + + sub = await dispatch_wrapper.get_subscriber(query_id) + assert type(sub) == str + assert sub == subscriber + + """Test get_endpoint""" + + get_ep = await dispatch_wrapper.get_endpoint(query_id) + assert type(get_ep) == str + assert 'Fibonacci' in get_ep + + """Test get_status""" + # Further testing required + status = await dispatch_wrapper.get_status(query_id) + st = dispatch_contract.functions.getStatus(query_id).call() + assert status == st + + """Test get_user_query""" + + user_query = await dispatch_wrapper.get_user_query(query_id) + assert type(user_query) == str + assert user_query == provider['query'] + + """Test get_subscriber_onchain""" + # Further testing required + sub_on_chain = await dispatch_wrapper.get_subscriber_onchain(query_id) + assert sub_on_chain is False + + @pytest.mark.asyncio + async def test_get_cancel(self, provider, oracle, subscriber, w3, dispatch_contract, meta_config, query_config): + """ + Testing that the get_cancel function returns the cancelled query's block number during cancellation. + """ + tx_hash = await dispatch_wrapper.query_data(**query_config) + tx_receipt = w3.eth.getTransactionReceipt(tx_hash) + query_id = dispatch_contract.events.Incoming().processReceipt( + tx_receipt)[0]["args"]["id"] + + assert query_id + + block_number = tx_receipt['blockNumber'] + + config_cancel = { + 'query_id': query_id, + } + + cancel = await dispatch_wrapper.cancel_query(**config_cancel, **meta_config) + assert cancel + + get_cancel = await dispatch_wrapper.get_cancel(query_id) + assert get_cancel == block_number + 1