From 6f8a25f4a0de05f661a05237e7b42dad4ad6854d Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 6 Mar 2021 19:21:10 -0500 Subject: [PATCH 01/14] Refactored BaseContract and Utils. Added artifacts_dir test --- src/BaseContract/base_contract.py | 82 +++++-- src/BaseContract/utils.py | 58 +++-- tests/BaseContract/test_base_contract.py | 267 +++++++++++++++++++++++ 3 files changed, 366 insertions(+), 41 deletions(-) create mode 100644 tests/BaseContract/test_base_contract.py diff --git a/src/BaseContract/base_contract.py b/src/BaseContract/base_contract.py index fde613e6..16c40a77 100644 --- a/src/BaseContract/base_contract.py +++ b/src/BaseContract/base_contract.py @@ -1,24 +1,70 @@ -import utils from web3 import Web3 +from src.index import Artifacts +from utils import Utils + class BaseContract: - def __init__(self, artifact_name, network_id, network_provider, coordinator, address, web3): - self.name = artifact_name + provider: any + web3: any + contract: any + network_id: int + coordinator: any + artifact: any + name: str + address: str = 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, + contract: any = None, + address: str = None + ): 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 + if artifacts_dir is None: + self.artifact = Artifacts[artifact_name] + self.coor_artifact = Artifacts['ZAPCOORDINATOR'] 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) + artifacts: any = Utils.get_artifacts(artifacts_dir) + self.artifact = Utils.open_artifact_in_dir(artifacts[artifact_name]) + self.coor_artifact = 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']) + self.get_contract() + 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 \ No newline at end of file + raise e + + def get_contract(self) -> str: + """ + This function fetches the contract address from coordinator and assigns the 'self.contract' contract object. + :return: the contract address of the coordinator. + """ + contract_address = self.coordinator.functions.getContract(self.name.upper()).call() + self.contract = self.w3.eth.contract(address=self.w3.toChecksumAddress(contract_address), + abi=self.artifact['abi']) + return contract_address + + def get_contract_owner(self) -> str: + """ + This function fetches the owner of the contract instance. + :return: the contract owner's address. + """ + contract_owner = self.contract.functions.owner().call() + return contract_owner diff --git a/src/BaseContract/utils.py b/src/BaseContract/utils.py index 6244e404..45ef1ec7 100644 --- a/src/BaseContract/utils.py +++ b/src/BaseContract/utils.py @@ -1,29 +1,41 @@ -import json import os +import json -# 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 +class Utils: + """ + # @ignore + # @params {string} buildDir + # @returns {any} + """ -def load_abi(name: str) -> dict: - obj = load_json(name) - abi = obj['abi'] - return abi + @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 -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)) + """ + # @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/tests/BaseContract/test_base_contract.py b/tests/BaseContract/test_base_contract.py new file mode 100644 index 00000000..4b47a663 --- /dev/null +++ b/tests/BaseContract/test_base_contract.py @@ -0,0 +1,267 @@ +from unittest.mock import MagicMock, patch +import pytest +import base_contract + +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'}}} +} + + + +@patch.dict('base_contract.Artifacts', mock_abi) +@patch('base_contract.Web3', autospec=True) +class TestInit: + + def test_instance_name(self, mock_Web3): + """ + Sanity check to ensure the instance runs without errors while mocking web3 + + :param mock_Web3: patched web3. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') + + 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. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + with pytest.raises(KeyError): + instance = base_contract.BaseContract(artifact_name='') + + + def test_network_id_default(self, mock_Web3): + """ + Testing that the default network (1) is assigned to the contract instance. + + :param mock_Web3: patched web3. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') + + 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. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input) + + assert instance.network_id == input + + with pytest.raises(AssertionError): + assert instance.network_id != input + + @pytest.mark.parametrize('wrong_net_id', [11, 16, 47, 118893]) + def test_network_id_should_fail_if_given_unknown_id(self, mock_Web3, wrong_net_id): + """ + Testing that the contract should fail if given an unknown network id. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + with pytest.raises(KeyError): + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=wrong_net_id) + + def test_network_id_of_zero(self, mock_Web3): + """ + Testing that the network_id of zero actually uses the default network of '1.' + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=0) + + assert instance.network_id != 0 + assert instance.network_id == 1 + + +@patch.dict('base_contract.Artifacts', mock_abi) +@patch('base_contract.Web3', autospec=True) +class TestAddress: + + def test_address_argument(self, mock_Web3): + """ + Testing that the address argument is assigned as the instance address. + + :param mock_Web3: patched web3. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', address='0x_some_address') + + assert type(instance.address) == str + assert instance.address == '0x_some_address' + + with pytest.raises(AssertionError): + assert instance.address == '0xsomeotheraddress' + + @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. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=network_input) + + assert type(instance.address) == str + assert instance.address == address_output + + with pytest.raises(AssertionError): + assert instance.address == '0xwrongaddress' + + +@patch.dict('base_contract.Artifacts', mock_abi) +@patch('base_contract.Web3', autospec=True) +class TestArtifacts: + + mock_contract = MagicMock(address="0x000000000000000000", abi=['abi'], name="MOCKCONTRACT") + + def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3): + """ + 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. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') + + """Asserting the artifact attribute is equal to the mock dictionary""" + + 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' + + """Ensuring this includes no false positives""" + + 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('base_contract.Web3', autospec=True) +class TestArtifactsDirectory: + 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('base_contract.Utils.get_artifacts') + @patch('base_contract.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 BaseContract 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 mimicks 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 web3""" + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + """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) + + 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'] + + + + From fdd91bf9cd0ba67c4f696d565eae4019dfbc18d5 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 8 Mar 2021 04:43:56 -0500 Subject: [PATCH 02/14] Implemented asyncio in base_contract, still need to test --- src/BaseContract/base_contract.py | 18 ++++-- tests/BaseContract/test_base_contract.py | 72 +++++++++++++++++++----- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/BaseContract/base_contract.py b/src/BaseContract/base_contract.py index 16c40a77..435d6c50 100644 --- a/src/BaseContract/base_contract.py +++ b/src/BaseContract/base_contract.py @@ -1,6 +1,7 @@ from web3 import Web3 from src.index import Artifacts from utils import Utils +import asyncio class BaseContract: @@ -41,30 +42,39 @@ def __init__(self, if coordinator is not None: self.coordinator = self.w3.eth.contract(address=self.w3.toChecksumAddress(coordinator), abi=self.coor_artifact['abi']) - self.get_contract() + call_get_contract = self.get_contract() + asyncio.run(call_get_contract) 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']) + + call_contract_owner = self.get_contract_owner() + asyncio.run(call_contract_owner) + except Exception as e: raise e - def get_contract(self) -> str: + async def get_contract(self) -> str: """ This function fetches the contract address from coordinator and assigns the 'self.contract' contract object. :return: the contract address of the coordinator. """ - contract_address = self.coordinator.functions.getContract(self.name.upper()).call() + await asyncio.sleep(1) + contract_address = self.coordinator.functions.getContract.address + print('address is:' + contract_address) #DELETE: For demo purposes!!! self.contract = self.w3.eth.contract(address=self.w3.toChecksumAddress(contract_address), abi=self.artifact['abi']) return contract_address - def get_contract_owner(self) -> str: + async def get_contract_owner(self) -> str: """ This function fetches the owner of the contract instance. :return: the contract owner's address. """ + await asyncio.sleep(1) contract_owner = self.contract.functions.owner().call() + print('owner is:' + contract_owner) #DELETE: For demo purposes!!! return contract_owner diff --git a/tests/BaseContract/test_base_contract.py b/tests/BaseContract/test_base_contract.py index 4b47a663..b5396595 100644 --- a/tests/BaseContract/test_base_contract.py +++ b/tests/BaseContract/test_base_contract.py @@ -14,12 +14,11 @@ } - @patch.dict('base_contract.Artifacts', mock_abi) @patch('base_contract.Web3', autospec=True) class TestInit: - def test_instance_name(self, mock_Web3): + def test_instance_name_and_coordinator(self, mock_Web3): """ Sanity check to ensure the instance runs without errors while mocking web3 @@ -44,10 +43,11 @@ def test_name_without_arg(self, mock_Web3): """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + w3.eth.contract.return_value = w3._utils.datatypes.Contract with pytest.raises(KeyError): instance = base_contract.BaseContract(artifact_name='') + assert instance def test_network_id_default(self, mock_Web3): @@ -58,7 +58,7 @@ def test_network_id_default(self, mock_Web3): """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + w3.eth.contract.return_value = w3._utils.datatypes.Contract instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') @@ -79,7 +79,7 @@ def test_assigned_network_ids(self, mock_Web3, input): """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + w3.eth.contract.return_value = w3._utils.datatypes.Contract instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input) assert instance.network_id == input @@ -94,10 +94,11 @@ def test_network_id_should_fail_if_given_unknown_id(self, mock_Web3, wrong_net_i """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + w3.eth.contract.return_value = w3._utils.datatypes.Contract with pytest.raises(KeyError): instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=wrong_net_id) + assert instance def test_network_id_of_zero(self, mock_Web3): """ @@ -105,7 +106,7 @@ def test_network_id_of_zero(self, mock_Web3): """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + w3.eth.contract.return_value = w3._utils.datatypes.Contract instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=0) @@ -125,7 +126,7 @@ def test_address_argument(self, mock_Web3): """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + w3.eth.contract.return_value = w3._utils.datatypes.Contract instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', address='0x_some_address') @@ -146,7 +147,7 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + w3.eth.contract.return_value = w3._utils.datatypes.Contract instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=network_input) @@ -161,8 +162,6 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output @patch('base_contract.Web3', autospec=True) class TestArtifacts: - mock_contract = MagicMock(address="0x000000000000000000", abi=['abi'], name="MOCKCONTRACT") - def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3): """ Testing that the artifact_name kwarg triggers the Artifacts dictionary and populates the artifact @@ -172,7 +171,7 @@ def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3) """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + w3.eth.contract.return_value = w3._utils.datatypes.Contract instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') @@ -220,7 +219,7 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo respectively. Thereafter, the BaseContract 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 mimicks what open_artifacts_in_dir function will return. + :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. """ @@ -228,7 +227,7 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo """Mock web3""" mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + w3.eth.contract.return_value = w3._utils.datatypes.Contract """Mock the relevant function returns""" mock_artifact_dir.return_value = self.mock_dict_dir @@ -263,5 +262,50 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo assert instance.coor_artifact['abi']['network'] +@patch.dict('base_contract.Artifacts', mock_abi) +@patch('base_contract.Web3', autospec=True) +class TestContracts: + + def test_coordinator_contract_if_no_coor_provided(self, mock_Web3): + """ + Testing that the coordinator contract object is assigned. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = w3._utils.datatypes.Contract + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') + assert instance.coordinator + + def test_coordinator_contract_with_coordinator_address(self, mock_Web3): + """ + Testing that the coordinator contract object is assigned. + """ + + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = w3._utils.datatypes.Contract + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x0102030405') + + """Testing that the coordinator instance was assigned""" + + assert instance.coordinator + + + + def test_self_contract_if_no_coor_provided(self, mock_Web3): + """ + Testing that the contract instance object is assigned. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = w3._utils.datatypes.Contract + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') + assert instance.contract + + + From b81383b2587612f64f399ddcdba3f86d449a82e5 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 10 Mar 2021 02:36:23 -0500 Subject: [PATCH 03/14] Adjusted async in base_contract, added arg capture side effect in tests --- .../contracts/Arbiter.json | 0 .../contracts/Bondage.json | 0 .../contracts/Client1.json | 0 .../contracts/Client2.json | 0 .../contracts/Client3.json | 0 .../contracts/Client4.json | 0 .../contracts/ClientBytes32Array.json | 0 .../contracts/ClientIntArray.json | 0 .../contracts/CurrentCost.json | 0 .../contracts/Dispatch.json | 0 .../contracts/Piecewise.json | 0 .../contracts/Registry.json | 0 .../contracts/RegistryInterface.json | 0 .../contracts/TokenDotFactory.json | 0 .../contracts/TokenFactory.json | 0 .../contracts/ZapCoordinator.json | 0 .../contracts/ZapCoordinatorInterface.json | 0 .../contracts/ZapToken.json | 0 src/{Artifacts => artifacts}/src/index.py | 0 .../src/tests/__init__.py | 0 .../src/tests/test_artifacts.py | 0 .../base_contract.py | 62 ++++++++++--- .../test/__init__.py | 0 .../test/test_base_contract.py | 0 src/{BaseContract => base_contract}/utils.py | 0 .../test_base_contract.py | 89 ++++++++++++++++--- 26 files changed, 124 insertions(+), 27 deletions(-) rename src/{Artifacts => artifacts}/contracts/Arbiter.json (100%) rename src/{Artifacts => artifacts}/contracts/Bondage.json (100%) rename src/{Artifacts => artifacts}/contracts/Client1.json (100%) rename src/{Artifacts => artifacts}/contracts/Client2.json (100%) rename src/{Artifacts => artifacts}/contracts/Client3.json (100%) rename src/{Artifacts => artifacts}/contracts/Client4.json (100%) rename src/{Artifacts => artifacts}/contracts/ClientBytes32Array.json (100%) rename src/{Artifacts => artifacts}/contracts/ClientIntArray.json (100%) rename src/{Artifacts => artifacts}/contracts/CurrentCost.json (100%) rename src/{Artifacts => artifacts}/contracts/Dispatch.json (100%) rename src/{Artifacts => artifacts}/contracts/Piecewise.json (100%) rename src/{Artifacts => artifacts}/contracts/Registry.json (100%) rename src/{Artifacts => artifacts}/contracts/RegistryInterface.json (100%) rename src/{Artifacts => artifacts}/contracts/TokenDotFactory.json (100%) rename src/{Artifacts => artifacts}/contracts/TokenFactory.json (100%) rename src/{Artifacts => artifacts}/contracts/ZapCoordinator.json (100%) rename src/{Artifacts => artifacts}/contracts/ZapCoordinatorInterface.json (100%) rename src/{Artifacts => artifacts}/contracts/ZapToken.json (100%) rename src/{Artifacts => artifacts}/src/index.py (100%) rename src/{Artifacts => artifacts}/src/tests/__init__.py (100%) rename src/{Artifacts => artifacts}/src/tests/test_artifacts.py (100%) rename src/{BaseContract => base_contract}/base_contract.py (58%) rename src/{BaseContract => base_contract}/test/__init__.py (100%) rename src/{BaseContract => base_contract}/test/test_base_contract.py (100%) rename src/{BaseContract => base_contract}/utils.py (100%) rename tests/{BaseContract => base_contract}/test_base_contract.py (83%) 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/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/Artifacts/src/tests/__init__.py b/src/artifacts/src/tests/__init__.py similarity index 100% rename from src/Artifacts/src/tests/__init__.py rename to src/artifacts/src/tests/__init__.py diff --git a/src/Artifacts/src/tests/test_artifacts.py b/src/artifacts/src/tests/test_artifacts.py similarity index 100% rename from src/Artifacts/src/tests/test_artifacts.py rename to src/artifacts/src/tests/test_artifacts.py diff --git a/src/BaseContract/base_contract.py b/src/base_contract/base_contract.py similarity index 58% rename from src/BaseContract/base_contract.py rename to src/base_contract/base_contract.py index 435d6c50..265f5745 100644 --- a/src/BaseContract/base_contract.py +++ b/src/base_contract/base_contract.py @@ -40,41 +40,75 @@ def __init__(self, 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), + self.coor_address = self.w3.toChecksumAddress(coordinator) + self.coordinator = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.coor_address), abi=self.coor_artifact['abi']) - call_get_contract = self.get_contract() - asyncio.run(call_get_contract) + + 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']) - call_contract_owner = self.get_contract_owner() - asyncio.run(call_contract_owner) - except Exception as e: raise e - async def get_contract(self) -> str: + async def _get_contract(self) -> str: """ - This function fetches the contract address from coordinator and assigns the 'self.contract' contract object. + This async function fetches the contract address from the coordinator and assigns the contract object instance + to the coordinator (within the context of the conditional statement of where it's located). Further, :return: the contract address of the coordinator. """ await asyncio.sleep(1) contract_address = self.coordinator.functions.getContract.address - print('address is:' + contract_address) #DELETE: For demo purposes!!! - self.contract = self.w3.eth.contract(address=self.w3.toChecksumAddress(contract_address), - abi=self.artifact['abi']) return contract_address - async def get_contract_owner(self) -> str: + async def _get_contract_owner(self) -> str: """ - This function fetches the owner of the contract instance. + This async function fetches the owner of the contract instance. + :return: the contract owner's address. """ await asyncio.sleep(1) contract_owner = self.contract.functions.owner().call() - print('owner is:' + contract_owner) #DELETE: For demo purposes!!! return contract_owner + + def get_contract(self) -> str: + """ + A synchronous function that wraps the asynchronous _get_contract method. This provides flexibility. The + async function is used in the constructor to assign the coordinator address to the contract object; while in a + different context, a user can fetch the contract object's address. + + :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 + + +w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545')) +y = BaseContract(artifact_name='ARBITER', web3=w3, network_id=31337, + coordinator='0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0') +print(y.get_contract_owner()) + + + + diff --git a/src/BaseContract/test/__init__.py b/src/base_contract/test/__init__.py similarity index 100% rename from src/BaseContract/test/__init__.py rename to src/base_contract/test/__init__.py diff --git a/src/BaseContract/test/test_base_contract.py b/src/base_contract/test/test_base_contract.py similarity index 100% rename from src/BaseContract/test/test_base_contract.py rename to src/base_contract/test/test_base_contract.py diff --git a/src/BaseContract/utils.py b/src/base_contract/utils.py similarity index 100% rename from src/BaseContract/utils.py rename to src/base_contract/utils.py diff --git a/tests/BaseContract/test_base_contract.py b/tests/base_contract/test_base_contract.py similarity index 83% rename from tests/BaseContract/test_base_contract.py rename to tests/base_contract/test_base_contract.py index b5396595..81a2e8a0 100644 --- a/tests/BaseContract/test_base_contract.py +++ b/tests/base_contract/test_base_contract.py @@ -1,6 +1,8 @@ -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch, AsyncMock, PropertyMock import pytest +import unittest import base_contract +import re mock_abi = { 'TEST_ARTIFACT': {'abi': [], 'networks': @@ -18,7 +20,7 @@ @patch('base_contract.Web3', autospec=True) class TestInit: - def test_instance_name_and_coordinator(self, mock_Web3): + def test_instance_name(self, mock_Web3): """ Sanity check to ensure the instance runs without errors while mocking web3 @@ -134,7 +136,7 @@ def test_address_argument(self, mock_Web3): assert instance.address == '0x_some_address' with pytest.raises(AssertionError): - assert instance.address == '0xsomeotheraddress' + 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): @@ -155,7 +157,7 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output assert instance.address == address_output with pytest.raises(AssertionError): - assert instance.address == '0xwrongaddress' + assert instance.address == '0x_wrong_address' @patch.dict('base_contract.Artifacts', mock_abi) @@ -164,7 +166,7 @@ class TestArtifacts: def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3): """ - Testing that the artifact_name kwarg triggers the Artifacts dictionary and populates the artifact + 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. @@ -201,8 +203,8 @@ class TestArtifactsDirectory: 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'} + mock_dict_dir = {'TEST_ARTIFACT': 'artifacts/contracts/TestArtifact.json', + 'ZAPCOORDINATOR': 'artifacts/contracts/ZapCoordinator.json'} @patch('base_contract.Utils.get_artifacts') @patch('base_contract.Utils.open_artifact_in_dir') @@ -216,7 +218,7 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo coor_address_output): """ Testing that the artifacts_dir kwarg passes through both get_artifacts and open_artifact_in_dir functions - respectively. Thereafter, the BaseContract instance attribute 'artifact' should be assigned as an object + 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. @@ -266,16 +268,33 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo @patch('base_contract.Web3', autospec=True) class TestContracts: - def test_coordinator_contract_if_no_coor_provided(self, mock_Web3): + """Side effect function for checking args and kwargs passed""" + def capture_args(self, *args, **kwargs) -> any: + return args, kwargs + + @patch('base_contract.BaseContract.get_contract') + def test_coordinator_contract_if_no_coor_provided(self, mock_get_contract, mock_Web3): """ Testing that the coordinator contract object is assigned. """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract + w3.toChecksumAddress.side_effect = self.capture_args + w3.eth.contract.side_effect = self.capture_args + + mock_get_contract = MagicMock() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x_some_address') + + assert type(instance.coordinator) == tuple + assert instance.coordinator[1]['abi'] == [] + + expected_address = '0x_some_address' + + """Iterate through the returned tuple to find the passed address""" + res = re.search(expected_address, str(instance.coordinator[1]['address'])) + assert res is not None - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') - assert instance.coordinator def test_coordinator_contract_with_coordinator_address(self, mock_Web3): """ @@ -294,7 +313,7 @@ def test_coordinator_contract_with_coordinator_address(self, mock_Web3): - def test_self_contract_if_no_coor_provided(self, mock_Web3): + def test_contract_instance_if_no_coor_provided(self, mock_Web3): """ Testing that the contract instance object is assigned. """ @@ -306,6 +325,50 @@ def test_self_contract_if_no_coor_provided(self, mock_Web3): assert instance.contract +@patch.dict('base_contract.Artifacts', mock_abi) +@patch('base_contract.Web3', autospec=True) +class TestMethods: + + + def mock_get_contract(self): + return '0xcoormainnet' + + @patch('base_contract.asyncio') + @patch('base_contract.BaseContract._get_contract') + def test_get_contract(self, mock_async_get, mock_asyncio, mock_Web3): + """ + Testing that the get_contract function returns the proper values. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = w3._utils.datatypes.Contract + + """Mock the async function call""" + mock_async_get.return_value = AsyncMock() + + """Mock asyncio""" + mock_asyncio.return_value = MagicMock() + m_asyncio = mock_asyncio() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0xcoormainnet') + + + # FINISH THIS TEST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + + + + + + + + + + + + + + From c6cf62304c8cddf5ca331f3d5efdeba5ad07e512 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 11 Mar 2021 21:28:45 -0500 Subject: [PATCH 04/14] Working on imports --- src/base_contract/base_contract.py | 34 +-- tests/base_contract/test_base_contract.py | 284 +++++++++++++++++----- 2 files changed, 239 insertions(+), 79 deletions(-) diff --git a/src/base_contract/base_contract.py b/src/base_contract/base_contract.py index 265f5745..4c57e406 100644 --- a/src/base_contract/base_contract.py +++ b/src/base_contract/base_contract.py @@ -1,6 +1,10 @@ from web3 import Web3 -from src.index import Artifacts -from utils import Utils +import sys +#from os.path import join, realpath +#sys.path.insert(0, realpath(join(__file__, "../../../src/"))) +from artifacts.src.index import Artifacts +#from src import index +import utils import asyncio @@ -29,9 +33,9 @@ def __init__(self, self.artifact = Artifacts[artifact_name] self.coor_artifact = Artifacts['ZAPCOORDINATOR'] else: - artifacts: any = Utils.get_artifacts(artifacts_dir) - self.artifact = Utils.open_artifact_in_dir(artifacts[artifact_name]) - self.coor_artifact = Utils.open_artifact_in_dir(artifacts['ZAPCOORDINATOR']) + 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")) @@ -40,12 +44,10 @@ def __init__(self, self.address = address or self.artifact['networks'][str(self.network_id)]['address'] if coordinator is not None: - self.coor_address = self.w3.toChecksumAddress(coordinator) - self.coordinator = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.coor_address), + 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']) @@ -66,8 +68,8 @@ async def _get_contract(self) -> str: to the coordinator (within the context of the conditional statement of where it's located). Further, :return: the contract address of the coordinator. """ - await asyncio.sleep(1) - contract_address = self.coordinator.functions.getContract.address + await asyncio.sleep(.5) + contract_address = self.coordinator.functions.getContract(self.name).call() return contract_address async def _get_contract_owner(self) -> str: @@ -76,7 +78,7 @@ async def _get_contract_owner(self) -> str: :return: the contract owner's address. """ - await asyncio.sleep(1) + await asyncio.sleep(.5) contract_owner = self.contract.functions.owner().call() return contract_owner @@ -102,13 +104,3 @@ def get_contract_owner(self) -> str: task = self._get_contract_owner() owner = asyncio.run(task) return owner - - -w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545')) -y = BaseContract(artifact_name='ARBITER', web3=w3, network_id=31337, - coordinator='0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0') -print(y.get_contract_owner()) - - - - diff --git a/tests/base_contract/test_base_contract.py b/tests/base_contract/test_base_contract.py index 81a2e8a0..c3c7225c 100644 --- a/tests/base_contract/test_base_contract.py +++ b/tests/base_contract/test_base_contract.py @@ -1,8 +1,20 @@ from unittest.mock import MagicMock, patch, AsyncMock, PropertyMock import pytest import unittest -import base_contract +from unittest import mock +from anyio import run +import anyio +import sys import re +import asyncio +from os.path import join, realpath +sys.path.insert(0, realpath(join(__file__, "../../../src/"))) + +from base_contract import BaseContract, Artifacts, Web3 + + + +print(sys.path[0]) mock_abi = { 'TEST_ARTIFACT': {'abi': [], 'networks': @@ -15,22 +27,31 @@ '31337': {'address': '0xcoordevnet'}}} } +@pytest.fixture +@patch('Web3', autospec=True) +def mock_web3(mock_Web3): + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.return_value = MagicMock() + + -@patch.dict('base_contract.Artifacts', mock_abi) -@patch('base_contract.Web3', autospec=True) + +@patch.dict('Artifacts', mock_abi) +@patch('Web3', autospec=True) class TestInit: - def test_instance_name(self, mock_Web3): + def test_instance_name(self, mock_web3): """ Sanity check to ensure the instance runs without errors while mocking web3 :param mock_Web3: patched web3. """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() + #mock_Web3.return_value = MagicMock() + #w3 = mock_Web3() + #w3.eth.contract.return_value = MagicMock() - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') + instance = BaseContract(artifact_name='TEST_ARTIFACT') assert type(instance.name) == str assert instance.name == 'TEST_ARTIFACT' @@ -48,21 +69,20 @@ def test_name_without_arg(self, mock_Web3): w3.eth.contract.return_value = w3._utils.datatypes.Contract with pytest.raises(KeyError): - instance = base_contract.BaseContract(artifact_name='') + instance = BaseContract(artifact_name='') assert instance def test_network_id_default(self, mock_Web3): """ Testing that the default network (1) is assigned to the contract instance. - :param mock_Web3: patched web3. """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') + instance = BaseContract(artifact_name='TEST_ARTIFACT') assert type(instance.network_id) == int assert instance.network_id == 1 @@ -82,7 +102,7 @@ def test_assigned_network_ids(self, mock_Web3, input): mock_Web3.return_value = MagicMock() w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input) + instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=input) assert instance.network_id == input @@ -99,7 +119,7 @@ def test_network_id_should_fail_if_given_unknown_id(self, mock_Web3, wrong_net_i w3.eth.contract.return_value = w3._utils.datatypes.Contract with pytest.raises(KeyError): - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=wrong_net_id) + instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=wrong_net_id) assert instance def test_network_id_of_zero(self, mock_Web3): @@ -110,14 +130,14 @@ def test_network_id_of_zero(self, mock_Web3): w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=0) + instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=0) assert instance.network_id != 0 assert instance.network_id == 1 -@patch.dict('base_contract.Artifacts', mock_abi) -@patch('base_contract.Web3', autospec=True) +@patch.dict('Artifacts', mock_abi) +@patch('Web3', autospec=True) class TestAddress: def test_address_argument(self, mock_Web3): @@ -130,7 +150,7 @@ def test_address_argument(self, mock_Web3): w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', address='0x_some_address') + instance = BaseContract(artifact_name='TEST_ARTIFACT', address='0x_some_address') assert type(instance.address) == str assert instance.address == '0x_some_address' @@ -151,7 +171,7 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=network_input) + instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=network_input) assert type(instance.address) == str assert instance.address == address_output @@ -160,8 +180,8 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output assert instance.address == '0x_wrong_address' -@patch.dict('base_contract.Artifacts', mock_abi) -@patch('base_contract.Web3', autospec=True) +@patch.dict('Artifacts', mock_abi) +@patch('Web3', autospec=True) class TestArtifacts: def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3): @@ -175,7 +195,7 @@ def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3) w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') + instance = BaseContract(artifact_name='TEST_ARTIFACT') """Asserting the artifact attribute is equal to the mock dictionary""" @@ -195,7 +215,7 @@ def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3) assert instance.coor_artifact['networks']['1']['address'] == '0x123' -@patch('base_contract.Web3', autospec=True) +@patch('Web3', autospec=True) class TestArtifactsDirectory: mock_test_abi = {'abi': [], 'networks': {'1': {'address': '0xmainnet'}, '42': {'address': '0xkovan'}, '31337': {'address': '0xdevnet'}}} @@ -206,8 +226,8 @@ class TestArtifactsDirectory: mock_dict_dir = {'TEST_ARTIFACT': 'artifacts/contracts/TestArtifact.json', 'ZAPCOORDINATOR': 'artifacts/contracts/ZapCoordinator.json'} - @patch('base_contract.Utils.get_artifacts') - @patch('base_contract.Utils.open_artifact_in_dir') + @patch('Utils.get_artifacts') + @patch('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'), @@ -235,7 +255,7 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo 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/', + instance = BaseContract(artifact_name=art_input, artifacts_dir='some/path/', network_id=net_id_input) assert type(instance.artifact) == dict @@ -264,98 +284,246 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo assert instance.coor_artifact['abi']['network'] -@patch.dict('base_contract.Artifacts', mock_abi) -@patch('base_contract.Web3', autospec=True) +@patch.dict('Artifacts', mock_abi) +@patch('Web3', autospec=True) class TestContracts: """Side effect function for checking args and kwargs passed""" def capture_args(self, *args, **kwargs) -> any: return args, kwargs - @patch('base_contract.BaseContract.get_contract') - def test_coordinator_contract_if_no_coor_provided(self, mock_get_contract, mock_Web3): + @patch('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. + Testing that the coordinator contract object is assigned with the correct args. """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() w3.toChecksumAddress.side_effect = self.capture_args w3.eth.contract.side_effect = self.capture_args + """Mocking the get_contract function so the contract instance runs without error""" mock_get_contract = MagicMock() - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x_some_address') + instance = BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x_some_address', + network_id=net_id) 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 parenthesis hence the utilization of regexp. + """ + expected_address = '0x_some_address' - """Iterate through the returned tuple to find the passed address""" res = re.search(expected_address, str(instance.coordinator[1]['address'])) assert res is not None + """Testing for false positive""" + + with pytest.raises(AssertionError): + res = re.search('this_should_fail', str(instance.coordinator[1]['address'])) + assert res is not None - def test_coordinator_contract_with_coordinator_address(self, mock_Web3): + @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. + Testing that the coordinator contract object is assigned with the correct args. """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract + w3.toChecksumAddress.side_effect = self.capture_args + w3.eth.contract.side_effect = self.capture_args - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x0102030405') + instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id) """Testing that the coordinator instance was assigned""" - assert instance.coordinator + 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('BaseContract.get_contract') + def test_contract_instance_with_coor_through_get_contract_method(self, mock_get_contract, mock_Web3): - def test_contract_instance_if_no_coor_provided(self, mock_Web3): """ - Testing that the contract instance object is assigned. + Testing that the return value from get_contract passes through to the contract instance object. """ + + test_address = '0x_some_address' + mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract + w3.toChecksumAddress.side_effect = self.capture_args + w3.eth.contract.side_effect = self.capture_args + + """Mocking the get_contract function to return the expected value""" + + mock_get_contract.return_value = test_address - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT') - assert instance.contract + instance = BaseContract(artifact_name='TEST_ARTIFACT', coordinator=test_address) + """Actual test that iterates through the returned arguments from the web3 side effect""" -@patch.dict('base_contract.Artifacts', mock_abi) -@patch('base_contract.Web3', autospec=True) + 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. + """ + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.toChecksumAddress.side_effect = self.capture_args + w3.eth.contract.side_effect = self.capture_args + + instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id) + + 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('Artifacts', mock_abi) +@patch('Web3', autospec=True) class TestMethods: + """Side effect function for checking args and kwargs passed""" - def mock_get_contract(self): - return '0xcoormainnet' + def capture_args(self, *args, **kwargs) -> any: + return args, kwargs - @patch('base_contract.asyncio') - @patch('base_contract.BaseContract._get_contract') - def test_get_contract(self, mock_async_get, mock_asyncio, mock_Web3): + @patch('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. + Testing that the get_contract function returns the proper values from the _get_contract async function. """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract + w3.eth.contract.side_effect = self.capture_args + w3.toChecksumAddress.side_effect = self.capture_args + + """ + Mock the async _get_contract function and give 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 = BaseContract(artifact_name='ARBITER', coordinator='some_address') + + 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('BaseContract._get_contract_owner') + def test_sync_get_contract_owner(self, mock_async_owner, mock_Web3): + """ + Testing that the get_contract_owner returns the proper values from the async _get_contract_owner function. + """ + mock_Web3.return_value = MagicMock() + + """ + Mock the async _get_contract_owner function and give it a return value to check that the get_contract_owner + wrapper returns the proper value. + """ + mock_async_owner.return_value = 'test_owner_address' + + instance = BaseContract(artifact_name='TEST_ARTIFACT') + + assert type(instance.get_contract_owner()) == str + assert instance.get_contract_owner() == 'test_owner_address' - """Mock the async function call""" - mock_async_get.return_value = AsyncMock() +@pytest.fixture +def anyio_backend(): + """ Ensures anyio uses the default, pytest-asyncio plugin + for running async tests + """ + return 'asyncio' +@patch.dict('Artifacts', mock_abi) - """Mock asyncio""" - mock_asyncio.return_value = MagicMock() - m_asyncio = mock_asyncio() +@patch('Web3', autospec=True) +class TestAsyncs: - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0xcoormainnet') + def capture_args(self, *args, **kwargs) -> any: + """Side effect function for checking args and kwargs passed""" + return args, kwargs + + + def mock_owner_abi(self, *args, **kwargs): + """ + A mocked abi for the async _get_contract_owner to fetch from without being read by web3--hence it being + a class and not a dictionary. + """ + return_val = 'some_string' + + class Irrelevant: + class functions: + class owner: + def call(self): + return return_val + return Irrelevant + + def test_get_contract_owner_type(self, mock_Web3): + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = self.capture_args + w3.toChecksumAddress.side_effect = self.capture_args - # FINISH THIS TEST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + """Create instance to allow the coordinator attribute to be visible""" + instance = BaseContract(artifact_name='TEST_ARTIFACT') + async_owner = instance._get_contract_owner() + isinstance(async_owner, type(object)) + assert asyncio.iscoroutine(async_owner) is True + + @pytest.mark.anyio + async def test_get_contract_type(self, mock_Web3): + mock_Web3.return_value = MagicMock() + + instance = BaseContract(artifact_name='TEST_ARTIFACT') + async_contract = await instance._get_contract() + + isinstance(async_contract, type(object)) + assert asyncio.iscoroutine(async_contract) is True + + + + + + def test_async_get_contract_owner(self, mock_Web3): + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = self.mock_owner_abi + w3.toChecksumAddress.side_effect = self.mock_owner_abi + instance = BaseContract(artifact_name='TEST_ARTIFACT') + assert instance.get_contract_owner() == 'some_string' From 944912d08373ee0c404a6a81dc311c86a21bd33e Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 12 Mar 2021 23:46:51 -0500 Subject: [PATCH 05/14] Refactored imports, pytest runs from root --- src/artifacts/src/{tests => }/__init__.py | 0 src/artifacts/src/tests/test_artifacts.py | 34 --- src/base_contract/__init__.py | 2 + src/base_contract/base_contract.py | 12 +- src/base_contract/test/test_base_contract.py | 9 - src/portedFiles/default_tx_test.py | 9 - src/portedFiles/filter_test.py | 13 -- .../test => tests/base_contract}/__init__.py | 0 tests/base_contract/test_base_contract.py | 198 ++++++++---------- 9 files changed, 97 insertions(+), 180 deletions(-) rename src/artifacts/src/{tests => }/__init__.py (100%) delete mode 100644 src/artifacts/src/tests/test_artifacts.py create mode 100644 src/base_contract/__init__.py delete mode 100644 src/base_contract/test/test_base_contract.py delete mode 100644 src/portedFiles/default_tx_test.py delete mode 100644 src/portedFiles/filter_test.py rename {src/base_contract/test => tests/base_contract}/__init__.py (100%) 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/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/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 index 4c57e406..3c9eb0e8 100644 --- a/src/base_contract/base_contract.py +++ b/src/base_contract/base_contract.py @@ -1,10 +1,6 @@ from web3 import Web3 -import sys -#from os.path import join, realpath -#sys.path.insert(0, realpath(join(__file__, "../../../src/"))) -from artifacts.src.index import Artifacts -#from src import index -import utils +import src.artifacts.src.index as index +import src.base_contract.utils as utils import asyncio @@ -30,8 +26,8 @@ def __init__(self, ): try: if artifacts_dir is None: - self.artifact = Artifacts[artifact_name] - self.coor_artifact = Artifacts['ZAPCOORDINATOR'] + 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]) diff --git a/src/base_contract/test/test_base_contract.py b/src/base_contract/test/test_base_contract.py deleted file mode 100644 index e32a0fbe..00000000 --- a/src/base_contract/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/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/src/base_contract/test/__init__.py b/tests/base_contract/__init__.py similarity index 100% rename from src/base_contract/test/__init__.py rename to tests/base_contract/__init__.py diff --git a/tests/base_contract/test_base_contract.py b/tests/base_contract/test_base_contract.py index c3c7225c..2ac38a89 100644 --- a/tests/base_contract/test_base_contract.py +++ b/tests/base_contract/test_base_contract.py @@ -1,5 +1,6 @@ from unittest.mock import MagicMock, patch, AsyncMock, PropertyMock import pytest +import pytest_mock import unittest from unittest import mock from anyio import run @@ -7,14 +8,7 @@ import sys import re import asyncio -from os.path import join, realpath -sys.path.insert(0, realpath(join(__file__, "../../../src/"))) - -from base_contract import BaseContract, Artifacts, Web3 - - - -print(sys.path[0]) +import src.base_contract.base_contract as base_contract mock_abi = { 'TEST_ARTIFACT': {'abi': [], 'networks': @@ -27,31 +21,26 @@ '31337': {'address': '0xcoordevnet'}}} } -@pytest.fixture -@patch('Web3', autospec=True) -def mock_web3(mock_Web3): - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() +artifacts_dict = base_contract.index.Artifacts +mocked_web3 = 'src.base_contract.Web3' - -@patch.dict('Artifacts', mock_abi) -@patch('Web3', autospec=True) +@patch.dict(artifacts_dict, mock_abi, clear=True) +@patch(mocked_web3, autospec=True) class TestInit: - def test_instance_name(self, mock_web3): + def test_instance_name(self, mock_Web3): """ Sanity check to ensure the instance runs without errors while mocking web3 :param mock_Web3: patched web3. """ - #mock_Web3.return_value = MagicMock() - #w3 = mock_Web3() - #w3.eth.contract.return_value = MagicMock() + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() - instance = BaseContract(artifact_name='TEST_ARTIFACT') + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) assert type(instance.name) == str assert instance.name == 'TEST_ARTIFACT' @@ -69,10 +58,9 @@ def test_name_without_arg(self, mock_Web3): w3.eth.contract.return_value = w3._utils.datatypes.Contract with pytest.raises(KeyError): - instance = BaseContract(artifact_name='') + instance = base_contract.BaseContract(artifact_name='', web3=w3) assert instance - def test_network_id_default(self, mock_Web3): """ Testing that the default network (1) is assigned to the contract instance. @@ -82,7 +70,7 @@ def test_network_id_default(self, mock_Web3): w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = BaseContract(artifact_name='TEST_ARTIFACT') + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) assert type(instance.network_id) == int assert instance.network_id == 1 @@ -102,7 +90,7 @@ def test_assigned_network_ids(self, mock_Web3, input): mock_Web3.return_value = MagicMock() w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=input) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input, web3=w3) assert instance.network_id == input @@ -119,7 +107,7 @@ def test_network_id_should_fail_if_given_unknown_id(self, mock_Web3, wrong_net_i w3.eth.contract.return_value = w3._utils.datatypes.Contract with pytest.raises(KeyError): - instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=wrong_net_id) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=wrong_net_id, web3=w3) assert instance def test_network_id_of_zero(self, mock_Web3): @@ -130,14 +118,14 @@ def test_network_id_of_zero(self, mock_Web3): w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=0) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=0, web3=w3) assert instance.network_id != 0 assert instance.network_id == 1 -@patch.dict('Artifacts', mock_abi) -@patch('Web3', autospec=True) +@patch.dict(artifacts_dict, mock_abi, clear=True) +@patch(mocked_web3, autospec=False) class TestAddress: def test_address_argument(self, mock_Web3): @@ -148,9 +136,9 @@ def test_address_argument(self, mock_Web3): """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract + w3.eth.contract.return_value = MagicMock() - instance = BaseContract(artifact_name='TEST_ARTIFACT', address='0x_some_address') + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', address='0x_some_address', web3=w3) assert type(instance.address) == str assert instance.address == '0x_some_address' @@ -169,9 +157,9 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract + w3.eth.contract.side_effect = MagicMock() - instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=network_input) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=network_input, web3=w3) assert type(instance.address) == str assert instance.address == address_output @@ -180,8 +168,8 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output assert instance.address == '0x_wrong_address' -@patch.dict('Artifacts', mock_abi) -@patch('Web3', autospec=True) +@patch.dict(artifacts_dict, mock_abi, clear=True) +@patch(mocked_web3, autospec=False) class TestArtifacts: def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3): @@ -195,7 +183,7 @@ def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3) w3 = mock_Web3() w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = BaseContract(artifact_name='TEST_ARTIFACT') + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) """Asserting the artifact attribute is equal to the mock dictionary""" @@ -215,7 +203,7 @@ def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3) assert instance.coor_artifact['networks']['1']['address'] == '0x123' -@patch('Web3', autospec=True) +@patch(mocked_web3, autospec=False) class TestArtifactsDirectory: mock_test_abi = {'abi': [], 'networks': {'1': {'address': '0xmainnet'}, '42': {'address': '0xkovan'}, '31337': {'address': '0xdevnet'}}} @@ -226,8 +214,8 @@ class TestArtifactsDirectory: mock_dict_dir = {'TEST_ARTIFACT': 'artifacts/contracts/TestArtifact.json', 'ZAPCOORDINATOR': 'artifacts/contracts/ZapCoordinator.json'} - @patch('Utils.get_artifacts') - @patch('Utils.open_artifact_in_dir') + @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'), @@ -255,8 +243,8 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo mock_artifact_dir.return_value = self.mock_dict_dir mock_utils_abi.side_effect = [self.mock_test_abi, self.mock_coor_abi] - instance = BaseContract(artifact_name=art_input, artifacts_dir='some/path/', - network_id=net_id_input) + instance = base_contract.BaseContract(artifact_name=art_input, artifacts_dir='some/path/', + network_id=net_id_input, web3=w3) assert type(instance.artifact) == dict assert type(instance.artifact['abi']) == list @@ -284,15 +272,15 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo assert instance.coor_artifact['abi']['network'] -@patch.dict('Artifacts', mock_abi) -@patch('Web3', autospec=True) +@patch.dict(artifacts_dict, mock_abi, clear=True) +@patch(mocked_web3, autospec=False) class TestContracts: """Side effect function for checking args and kwargs passed""" def capture_args(self, *args, **kwargs) -> any: return args, kwargs - @patch('BaseContract.get_contract') + @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): """ @@ -306,8 +294,8 @@ def test_coordinator_contract_args_with_coordinator_address_provided(self, mock_ """Mocking the get_contract function so the contract instance runs without error""" mock_get_contract = MagicMock() - instance = BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x_some_address', - network_id=net_id) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x_some_address', + network_id=net_id, web3=w3) assert type(instance.coordinator) == tuple assert instance.coordinator[1]['abi'] == [] @@ -340,7 +328,7 @@ def test_coordinator_contract_without_coordinator_address_provided(self, mock_We w3.toChecksumAddress.side_effect = self.capture_args w3.eth.contract.side_effect = self.capture_args - instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id, web3=w3) """Testing that the coordinator instance was assigned""" @@ -354,7 +342,7 @@ def test_coordinator_contract_without_coordinator_address_provided(self, mock_We res = re.search('this_should_fail', str(instance.coordinator[1]['address'])) assert res is not None - @patch('BaseContract.get_contract') + @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): """ @@ -372,7 +360,7 @@ def test_contract_instance_with_coor_through_get_contract_method(self, mock_get_ mock_get_contract.return_value = test_address - instance = BaseContract(artifact_name='TEST_ARTIFACT', coordinator=test_address) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator=test_address, web3=w3) """Actual test that iterates through the returned arguments from the web3 side effect""" @@ -390,7 +378,7 @@ def test_contract_instance_if_no_coor_provided(self, mock_Web3, input_id, expect w3.toChecksumAddress.side_effect = self.capture_args w3.eth.contract.side_effect = self.capture_args - instance = BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id, web3=w3) assert type(instance.contract) == tuple assert instance.contract[1]['abi'] == [] @@ -404,8 +392,8 @@ def test_contract_instance_if_no_coor_provided(self, mock_Web3, input_id, expect assert res is not None -@patch.dict('Artifacts', mock_abi) -@patch('Web3', autospec=True) +@patch.dict(artifacts_dict, mock_abi, clear=True) +@patch(mocked_web3, autospec=False) class TestMethods: """Side effect function for checking args and kwargs passed""" @@ -413,7 +401,7 @@ class TestMethods: def capture_args(self, *args, **kwargs) -> any: return args, kwargs - @patch('BaseContract._get_contract') + @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. @@ -429,7 +417,7 @@ def test_sync_get_contract(self, mock_async_get_contract, mock_Web3): """ mock_async_get_contract.return_value = 'test_get_contract_address' - instance = BaseContract(artifact_name='ARBITER', coordinator='some_address') + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='some_address', web3=w3) assert type(instance.get_contract()) == str assert instance.get_contract() == 'test_get_contract_address' @@ -440,12 +428,15 @@ def test_sync_get_contract(self, mock_async_get_contract, mock_Web3): assert instance.get_contract() == 'this_should_fail' - @patch('BaseContract._get_contract_owner') + @patch('src.base_contract.base_contract.BaseContract._get_contract_owner') def test_sync_get_contract_owner(self, mock_async_owner, mock_Web3): """ Testing that the get_contract_owner returns the proper values from the async _get_contract_owner function. """ mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = self.capture_args + w3.toChecksumAddress.side_effect = self.capture_args """ Mock the async _get_contract_owner function and give it a return value to check that the get_contract_owner @@ -453,90 +444,83 @@ def test_sync_get_contract_owner(self, mock_async_owner, mock_Web3): """ mock_async_owner.return_value = 'test_owner_address' - instance = BaseContract(artifact_name='TEST_ARTIFACT') + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) assert type(instance.get_contract_owner()) == str assert instance.get_contract_owner() == 'test_owner_address' -@pytest.fixture -def anyio_backend(): - """ Ensures anyio uses the default, pytest-asyncio plugin - for running async tests - """ - return 'asyncio' -@patch.dict('Artifacts', mock_abi) -@patch('Web3', autospec=True) + +@patch.dict(artifacts_dict, mock_abi, clear=True) +@patch(mocked_web3, autospec=False) class TestAsyncs: + """Side effect function for checking args and kwargs passed""" + def capture_args(self, *args, **kwargs) -> any: - """Side effect function for checking args and kwargs passed""" return args, kwargs - - def mock_owner_abi(self, *args, **kwargs): - """ - A mocked abi for the async _get_contract_owner to fetch from without being read by web3--hence it being - a class and not a dictionary. - """ - return_val = 'some_string' - - class Irrelevant: - class functions: - class owner: - def call(self): - return return_val - return Irrelevant - - def test_get_contract_owner_type(self, mock_Web3): + async def get_contract_owner(self, mock_Web3): mock_Web3.return_value = MagicMock() w3 = mock_Web3() w3.eth.contract.side_effect = self.capture_args w3.toChecksumAddress.side_effect = self.capture_args - """Create instance to allow the coordinator attribute to be visible""" - instance = BaseContract(artifact_name='TEST_ARTIFACT') - async_owner = instance._get_contract_owner() - isinstance(async_owner, type(object)) - assert asyncio.iscoroutine(async_owner) is True - - @pytest.mark.anyio - async def test_get_contract_type(self, mock_Web3): - mock_Web3.return_value = MagicMock() + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) + res = await instance._get_contract_owner() + return res - instance = BaseContract(artifact_name='TEST_ARTIFACT') - async_contract = await instance._get_contract() - isinstance(async_contract, type(object)) - assert asyncio.iscoroutine(async_contract) is True - - - - - - def test_async_get_contract_owner(self, mock_Web3): - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.side_effect = self.mock_owner_abi - w3.toChecksumAddress.side_effect = self.mock_owner_abi - instance = BaseContract(artifact_name='TEST_ARTIFACT') - assert instance.get_contract_owner() == 'some_string' +#class TestAsyncs: +# def capture_args(self, *args, **kwargs) -> any: +# """Side effect function for checking args and kwargs passed""" +# return args, kwargs +# def mock_owner_abi(self, *args, **kwargs): +# """ +# A mocked abi for the async _get_contract_owner to fetch from without being read by web3--hence it being +# a class and not a dictionary. +# """ +# return_val = 'some_string' +# class Irrelevant: +# class functions: +# class owner: +# def call(self): +# return return_val +# return Irrelevant +# @pytest.mark.anyio +# async def test_get_contract_owner_type(self, mock_Web3): +# mock_Web3.return_value = MagicMock() +# w3 = mock_Web3() +# w3.eth.contract.side_effect = self.capture_args +# w3.toChecksumAddress.side_effect = self.capture_args +# """Create instance to allow the coordinator attribute to be visible""" +# instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) +# async_owner = instance._get_contract_owner() +# isinstance(async_owner, type(object)) +# assert asyncio.iscoroutine(async_owner) is True +# @pytest.mark.anyio +# async def test_get_contract_type(self, mock_Web3): +# mock_Web3.return_value = MagicMock() +# instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) +# async_contract = await instance._get_contract() +# isinstance(async_contract, type(object)) +# assert asyncio.iscoroutine(async_contract) is True From d48573b8a5fe8e97fab4e358de062dba13379894 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 14 Mar 2021 01:51:19 -0500 Subject: [PATCH 06/14] Fixed imports, finished testing base contract --- src/base_contract/base_contract.py | 14 +- tests/base_contract/test_base_contract.py | 376 ++++++++++------------ 2 files changed, 178 insertions(+), 212 deletions(-) diff --git a/src/base_contract/base_contract.py b/src/base_contract/base_contract.py index 3c9eb0e8..ef073a44 100644 --- a/src/base_contract/base_contract.py +++ b/src/base_contract/base_contract.py @@ -1,7 +1,8 @@ from web3 import Web3 +import asyncio + import src.artifacts.src.index as index import src.base_contract.utils as utils -import asyncio class BaseContract: @@ -21,7 +22,6 @@ def __init__(self, network_provider: any = None, artifacts_dir: str = None, coordinator: str = None, - contract: any = None, address: str = None ): try: @@ -60,8 +60,9 @@ def __init__(self, async def _get_contract(self) -> str: """ - This async function fetches the contract address from the coordinator and assigns the contract object instance - to the coordinator (within the context of the conditional statement of where it's located). Further, + 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) @@ -80,9 +81,8 @@ async def _get_contract_owner(self) -> str: def get_contract(self) -> str: """ - A synchronous function that wraps the asynchronous _get_contract method. This provides flexibility. The - async function is used in the constructor to assign the coordinator address to the contract object; while in a - different context, a user can fetch the contract object's address. + 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. """ diff --git a/tests/base_contract/test_base_contract.py b/tests/base_contract/test_base_contract.py index 2ac38a89..f743badf 100644 --- a/tests/base_contract/test_base_contract.py +++ b/tests/base_contract/test_base_contract.py @@ -1,16 +1,20 @@ -from unittest.mock import MagicMock, patch, AsyncMock, PropertyMock +""" + This module tests the different possible branches of execution within the Zap base contract. + + The following unit tests require minimal dependencies. Because the base contract embodies the constructor role for + the entire Python interface of Zap (Zappy), the focus here lies in testing the instantiated dynamic attributes + of the base contract class. +""" + +from unittest.mock import MagicMock, patch import pytest -import pytest_mock -import unittest -from unittest import mock -from anyio import run -import anyio -import sys import re -import asyncio import src.base_contract.base_contract as base_contract -mock_abi = { + +"""Default setup""" + +MOCK_ABI = { 'TEST_ARTIFACT': {'abi': [], 'networks': {'1': {'address': '0xmainnet'}, '42': {'address': '0xkovan'}, @@ -21,26 +25,31 @@ '31337': {'address': '0xcoordevnet'}}} } +ARTIFACTS_DICT = base_contract.index.Artifacts +WEB3 = 'src.base_contract.Web3' -artifacts_dict = base_contract.index.Artifacts -mocked_web3 = 'src.base_contract.Web3' + +def capture_args(*args, **kwargs) -> any: + """Side effect function for checking args and kwargs passed""" + return args, kwargs -@patch.dict(artifacts_dict, mock_abi, clear=True) -@patch(mocked_web3, autospec=True) +@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) +@patch(WEB3, autospec=True) class TestInit: - def test_instance_name(self, mock_Web3): - """ - Sanity check to ensure the instance runs without errors while mocking web3 + """Setup""" - :param mock_Web3: patched web3. - """ + with patch(WEB3) as mock_Web3: mock_Web3.return_value = MagicMock() w3 = mock_Web3() w3.eth.contract.side_effect = MagicMock() - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) + def test_instance_name(self, mock_Web3): + """ + Sanity check to ensure the instance runs without errors while mocking web3. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=self.w3) assert type(instance.name) == str assert instance.name == 'TEST_ARTIFACT' @@ -53,24 +62,17 @@ def test_name_without_arg(self, mock_Web3): """ Testing that the contract fails if the artifact_name kwarg is an empty string. """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract with pytest.raises(KeyError): - instance = base_contract.BaseContract(artifact_name='', web3=w3) + instance = base_contract.BaseContract(artifact_name='', web3=self.w3) assert instance def test_network_id_default(self, mock_Web3): """ Testing that the default network (1) is assigned to the contract instance. - :param mock_Web3: patched web3. """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=self.w3) assert type(instance.network_id) == int assert instance.network_id == 1 @@ -87,58 +89,52 @@ def test_assigned_network_ids(self, mock_Web3, input): :param mock_Web3: patched web3. :param input: the relevant networks' corresponding integer. """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input, web3=w3) + + 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]) + @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 should fail if given an unknown network id. + Testing that the contract fails if given an unknown network id. """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract with pytest.raises(KeyError): - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=wrong_net_id, web3=w3) + 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.' """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=0, web3=w3) + 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(artifacts_dict, mock_abi, clear=True) -@patch(mocked_web3, autospec=False) +@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) +@patch(WEB3, autospec=False) class TestAddress: + """Setup""" + + with patch(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. - - :param mock_Web3: patched web3. """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = MagicMock() - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', address='0x_some_address', web3=w3) + 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' @@ -153,13 +149,10 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output :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. + :param address_output: the associated address within the MOCK_ABI dictionary. """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.side_effect = MagicMock() - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=network_input, web3=w3) + 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 @@ -168,10 +161,17 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output assert instance.address == '0x_wrong_address' -@patch.dict(artifacts_dict, mock_abi, clear=True) -@patch(mocked_web3, autospec=False) +@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) +@patch(WEB3, autospec=True) class TestArtifacts: + """Setup""" + + with patch(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): """ Testing that the artifact_name kwarg triggers the artifacts dictionary and populates the artifact @@ -179,13 +179,7 @@ def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3) :param mock_Web3: patched web3. """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract - - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) - - """Asserting the artifact attribute is equal to the mock dictionary""" + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=self.w3) assert type(instance.artifact) == dict assert instance.artifact['networks']['1']['address'] == '0xmainnet' @@ -193,8 +187,6 @@ def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3) assert type(instance.coor_artifact) == dict assert instance.coor_artifact['networks']['1']['address'] == '0xcoormainnet' - """Ensuring this includes no false positives""" - with pytest.raises(AssertionError): assert type(instance.artifact) != dict assert instance.artifact['networks']['1']['address'] == '0x123' @@ -203,8 +195,16 @@ def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3) assert instance.coor_artifact['networks']['1']['address'] == '0x123' -@patch(mocked_web3, autospec=False) +@patch(WEB3, autospec=False) class TestArtifactsDirectory: + + """Setup""" + + with patch(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'}}} @@ -234,17 +234,12 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo :param mock_Web3: patched web3. """ - """Mock web3""" - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.return_value = w3._utils.datatypes.Contract - """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=w3) + network_id=net_id_input, web3=self.w3) assert type(instance.artifact) == dict assert type(instance.artifact['abi']) == list @@ -272,37 +267,36 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo assert instance.coor_artifact['abi']['network'] -@patch.dict(artifacts_dict, mock_abi, clear=True) -@patch(mocked_web3, autospec=False) +@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) +@patch(WEB3, autospec=False) class TestContracts: - """Side effect function for checking args and kwargs passed""" - def capture_args(self, *args, **kwargs) -> any: - return args, kwargs + """Setup""" + + with patch(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. + 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_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.toChecksumAddress.side_effect = self.capture_args - w3.eth.contract.side_effect = self.capture_args - - """Mocking the get_contract function so the contract instance runs without error""" mock_get_contract = MagicMock() instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x_some_address', - network_id=net_id, web3=w3) + 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 parenthesis hence the utilization of regexp. + kwargs include a lot of unnecessary punctuation--hence the use of regexp. """ expected_address = '0x_some_address' @@ -310,8 +304,6 @@ def test_coordinator_contract_args_with_coordinator_address_provided(self, mock_ res = re.search(expected_address, str(instance.coordinator[1]['address'])) assert res is not None - """Testing for false positive""" - with pytest.raises(AssertionError): res = re.search('this_should_fail', str(instance.coordinator[1]['address'])) assert res is not None @@ -320,17 +312,10 @@ def test_coordinator_contract_args_with_coordinator_address_provided(self, mock_ (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. + Testing that the coordinator contract object is assigned with the correct args. The first assertions test that + the coordinator instance was correctly assigned. """ - - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.toChecksumAddress.side_effect = self.capture_args - w3.eth.contract.side_effect = self.capture_args - - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id, web3=w3) - - """Testing that the coordinator instance was 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'] == [] @@ -344,25 +329,14 @@ def test_coordinator_contract_without_coordinator_address_provided(self, mock_We @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. + 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_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.toChecksumAddress.side_effect = self.capture_args - w3.eth.contract.side_effect = self.capture_args - - """Mocking the get_contract function to return the expected value""" - mock_get_contract.return_value = test_address - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator=test_address, web3=w3) - - """Actual test that iterates through the returned arguments from the web3 side effect""" + 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 @@ -370,15 +344,10 @@ def test_contract_instance_with_coor_through_get_contract_method(self, mock_get_ @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. + Testing that the contract instance object is assigned with the correct args using the MOCK_ABI with + different networks. """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.toChecksumAddress.side_effect = self.capture_args - w3.eth.contract.side_effect = self.capture_args - - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id, web3=w3) + 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'] == [] @@ -392,32 +361,28 @@ def test_contract_instance_if_no_coor_provided(self, mock_Web3, input_id, expect assert res is not None -@patch.dict(artifacts_dict, mock_abi, clear=True) -@patch(mocked_web3, autospec=False) -class TestMethods: +@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) +@patch(WEB3, autospec=False) +class TestWrapperMethods: - """Side effect function for checking args and kwargs passed""" + """Setup""" - def capture_args(self, *args, **kwargs) -> any: - return args, kwargs - - @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. - """ + with patch(WEB3) as mock_Web3: mock_Web3.return_value = MagicMock() w3 = mock_Web3() - w3.eth.contract.side_effect = self.capture_args - w3.toChecksumAddress.side_effect = self.capture_args + 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): """ - Mock the async _get_contract function and give it a return value to check that the get_contract + 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=w3) + 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' @@ -432,95 +397,96 @@ def test_sync_get_contract(self, mock_async_get_contract, mock_Web3): def test_sync_get_contract_owner(self, mock_async_owner, mock_Web3): """ Testing that the get_contract_owner returns the proper values from the async _get_contract_owner function. - """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.side_effect = self.capture_args - w3.toChecksumAddress.side_effect = self.capture_args - - """ - Mock the async _get_contract_owner function and give it a return value to check that the get_contract_owner - wrapper returns the proper value. + 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' - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=self.w3) 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' -@patch.dict(artifacts_dict, mock_abi, clear=True) -@patch(mocked_web3, autospec=False) -class TestAsyncs: - - - """Side effect function for checking args and kwargs passed""" - - def capture_args(self, *args, **kwargs) -> any: - return args, kwargs - - async def get_contract_owner(self, mock_Web3): - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - w3.eth.contract.side_effect = self.capture_args - w3.toChecksumAddress.side_effect = self.capture_args - - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) - res = await instance._get_contract_owner() - return res - - - - - +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 Irrelevant: + class functions: + class owner: + def call(self): + return return_val + return Irrelevant -#class TestAsyncs: + def mock_coor_abi(self, *args, **kwargs): + """Function mimicking abi""" + return_val = '0x_artifact_address' -# def capture_args(self, *args, **kwargs) -> any: -# """Side effect function for checking args and kwargs passed""" -# return args, kwargs + class mimicked_coordinator: + class functions: + class getContract: + def __init__(self, name): + self.name = name + def call(self): + return return_val + return mimicked_coordinator -# def mock_owner_abi(self, *args, **kwargs): -# """ -# A mocked abi for the async _get_contract_owner to fetch from without being read by web3--hence it being -# a class and not a dictionary. -# """ -# return_val = 'some_string' + @pytest.mark.asyncio + async def test_async_get_contract_owner(self): + """ + Testing the asynchronous _get_contract_owner function. + """ + with patch(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() -# class Irrelevant: -# class functions: -# class owner: -# def call(self): -# return return_val -# return Irrelevant + with patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True): -# @pytest.mark.anyio -# async def test_get_contract_owner_type(self, mock_Web3): -# mock_Web3.return_value = MagicMock() -# w3 = mock_Web3() -# w3.eth.contract.side_effect = self.capture_args -# w3.toChecksumAddress.side_effect = self.capture_args + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) + res = await instance._get_contract_owner() + assert res == '0x_owner_address' -# """Create instance to allow the coordinator attribute to be visible""" -# instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) -# async_owner = instance._get_contract_owner() -# isinstance(async_owner, type(object)) -# assert asyncio.iscoroutine(async_owner) is True + with pytest.raises(AssertionError): + assert res == 'this_should_fail' -# @pytest.mark.anyio -# async def test_get_contract_type(self, mock_Web3): -# mock_Web3.return_value = MagicMock() + @pytest.mark.asyncio + async def test_async_get_contract(self): + """ + Testing the asynchronous _get_contract function. + """ + with patch(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() -# instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) -# async_contract = await instance._get_contract() + with patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True): -# isinstance(async_contract, type(object)) -# assert asyncio.iscoroutine(async_contract) is 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' From 88627431e5ebecca0fa5f383317512b03d3387c1 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 14 Mar 2021 13:49:25 -0400 Subject: [PATCH 07/14] Refactored async decorators in test to use anyio --- tests/base_contract/test_base_contract.py | 52 ++++++++++++----------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/tests/base_contract/test_base_contract.py b/tests/base_contract/test_base_contract.py index f743badf..f1b42526 100644 --- a/tests/base_contract/test_base_contract.py +++ b/tests/base_contract/test_base_contract.py @@ -34,6 +34,26 @@ def capture_args(*args, **kwargs) -> any: return args, kwargs +@pytest.fixture +def anyio_backend(): + """ + Ensures anyio uses the default, pytest-asyncio plugin + for running async tests. + """ + return 'asyncio' + + +@pytest.fixture +@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) +@patch(WEB3, autospec=True) +def instance(mock_Web3): + with patch(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) + + @patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) @patch(WEB3, autospec=True) class TestInit: @@ -45,12 +65,10 @@ class TestInit: w3 = mock_Web3() w3.eth.contract.side_effect = MagicMock() - def test_instance_name(self, mock_Web3): + def test_instance_name(self, mock_Web3, instance): """ Sanity check to ensure the instance runs without errors while mocking web3. """ - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=self.w3) - assert type(instance.name) == str assert instance.name == 'TEST_ARTIFACT' @@ -62,18 +80,14 @@ 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): + def test_network_id_default(self, mock_Web3, instance): """ Testing that the default network (1) is assigned to the contract instance. """ - - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=self.w3) - assert type(instance.network_id) == int assert instance.network_id == 1 @@ -89,7 +103,6 @@ def test_assigned_network_ids(self, mock_Web3, input): :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 @@ -102,7 +115,6 @@ def test_network_id_should_fail_if_given_unknown_id(self, mock_Web3, wrong_net_i """ 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 @@ -111,7 +123,6 @@ 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 @@ -133,7 +144,6 @@ 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 @@ -151,7 +161,6 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output :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 @@ -172,15 +181,13 @@ class TestArtifacts: w3 = mock_Web3() w3.eth.contract.side_effect = MagicMock() - def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3): + 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. """ - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=self.w3) - assert type(instance.artifact) == dict assert instance.artifact['networks']['1']['address'] == '0xmainnet' @@ -394,7 +401,7 @@ def test_sync_get_contract(self, mock_async_get_contract, mock_Web3): @patch('src.base_contract.base_contract.BaseContract._get_contract_owner') - def test_sync_get_contract_owner(self, mock_async_owner, mock_Web3): + 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 @@ -402,8 +409,6 @@ def test_sync_get_contract_owner(self, mock_async_owner, mock_Web3): """ mock_async_owner.return_value = 'test_owner_address' - instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=self.w3) - assert type(instance.get_contract_owner()) == str assert instance.get_contract_owner() == 'test_owner_address' @@ -429,12 +434,12 @@ class TestAsyncMethods: def mock_owner_abi(self, *args, **kwargs): """Function mimicking abi""" return_val = '0x_owner_address' - class Irrelevant: + class mimicked_abi: class functions: class owner: def call(self): return return_val - return Irrelevant + return mimicked_abi def mock_coor_abi(self, *args, **kwargs): @@ -450,8 +455,7 @@ def call(self): return return_val return mimicked_coordinator - - @pytest.mark.asyncio + @pytest.mark.anyio async def test_async_get_contract_owner(self): """ Testing the asynchronous _get_contract_owner function. @@ -471,7 +475,7 @@ async def test_async_get_contract_owner(self): with pytest.raises(AssertionError): assert res == 'this_should_fail' - @pytest.mark.asyncio + @pytest.mark.anyio async def test_async_get_contract(self): """ Testing the asynchronous _get_contract function. From 386c1dd64be72b39a15b98336bc4c9bfc7194830 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 14 Mar 2021 15:12:44 -0400 Subject: [PATCH 08/14] Added conftest --- tests/base_contract/conftest.py | 37 ++++++++++++ tests/base_contract/test_base_contract.py | 68 ++++++++--------------- 2 files changed, 60 insertions(+), 45 deletions(-) create mode 100644 tests/base_contract/conftest.py diff --git a/tests/base_contract/conftest.py b/tests/base_contract/conftest.py new file mode 100644 index 00000000..486720fd --- /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(ARTIFACTS_DICT, MOCK_ABI, clear=True) +@patch(WEB3, autospec=True) +def instance(mock_Web3): + with patch(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 index f1b42526..8a93adcf 100644 --- a/tests/base_contract/test_base_contract.py +++ b/tests/base_contract/test_base_contract.py @@ -14,6 +14,7 @@ """Default setup""" + MOCK_ABI = { 'TEST_ARTIFACT': {'abi': [], 'networks': {'1': {'address': '0xmainnet'}, @@ -25,42 +26,18 @@ '31337': {'address': '0xcoordevnet'}}} } -ARTIFACTS_DICT = base_contract.index.Artifacts -WEB3 = 'src.base_contract.Web3' - - def capture_args(*args, **kwargs) -> any: """Side effect function for checking args and kwargs passed""" return args, kwargs -@pytest.fixture -def anyio_backend(): - """ - Ensures anyio uses the default, pytest-asyncio plugin - for running async tests. - """ - return 'asyncio' - - -@pytest.fixture -@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) -@patch(WEB3, autospec=True) -def instance(mock_Web3): - with patch(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) - - -@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) -@patch(WEB3, autospec=True) +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=True) class TestInit: """Setup""" - with patch(WEB3) as 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() @@ -129,13 +106,13 @@ def test_network_id_of_zero(self, mock_Web3): assert instance.network_id == 1 -@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) -@patch(WEB3, autospec=False) +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=False) class TestAddress: """Setup""" - with patch(WEB3) as 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() @@ -170,13 +147,13 @@ def test_address_with_no_argument(self, mock_Web3, network_input, address_output assert instance.address == '0x_wrong_address' -@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) -@patch(WEB3, autospec=True) +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=True) class TestArtifacts: """Setup""" - with patch(WEB3) as 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() @@ -202,12 +179,12 @@ def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3, assert instance.coor_artifact['networks']['1']['address'] == '0x123' -@patch(WEB3, autospec=False) +@patch('src.base_contract.Web3', autospec=False) class TestArtifactsDirectory: """Setup""" - with patch(WEB3) as 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() @@ -274,13 +251,13 @@ def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mo assert instance.coor_artifact['abi']['network'] -@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) -@patch(WEB3, autospec=False) +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=False) class TestContracts: """Setup""" - with patch(WEB3) as mock_Web3: + with patch('src.base_contract.Web3') as mock_Web3: mock_Web3.return_value = MagicMock() w3 = mock_Web3() w3.toChecksumAddress.side_effect = capture_args @@ -368,13 +345,13 @@ def test_contract_instance_if_no_coor_provided(self, mock_Web3, input_id, expect assert res is not None -@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) -@patch(WEB3, autospec=False) +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=False) class TestWrapperMethods: """Setup""" - with patch(WEB3) as mock_Web3: + with patch('src.base_contract.Web3') as mock_Web3: mock_Web3.return_value = MagicMock() w3 = mock_Web3() w3.toChecksumAddress.side_effect = capture_args @@ -431,6 +408,7 @@ class TestAsyncMethods: """Setup""" + def mock_owner_abi(self, *args, **kwargs): """Function mimicking abi""" return_val = '0x_owner_address' @@ -460,13 +438,13 @@ async def test_async_get_contract_owner(self): """ Testing the asynchronous _get_contract_owner function. """ - with patch(WEB3) as mock_Web3: + 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(ARTIFACTS_DICT, MOCK_ABI, clear=True): + 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() @@ -480,13 +458,13 @@ async def test_async_get_contract(self): """ Testing the asynchronous _get_contract function. """ - with patch(WEB3) as mock_Web3: + 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(ARTIFACTS_DICT, MOCK_ABI, clear=True): + 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() From e201456140f241ac0ab0caba81ba124115c77550 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 14 Mar 2021 19:51:46 -0400 Subject: [PATCH 09/14] Changed how web3 was patched, conftest also reflects these changes --- src/base_contract/base_contract.py | 2 +- tests/base_contract/conftest.py | 6 +++--- tests/base_contract/test_base_contract.py | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/base_contract/base_contract.py b/src/base_contract/base_contract.py index ef073a44..07dbb55a 100644 --- a/src/base_contract/base_contract.py +++ b/src/base_contract/base_contract.py @@ -13,7 +13,7 @@ class BaseContract: coordinator: any artifact: any name: str - address: str = None + address: str or None def __init__(self, artifact_name: str, diff --git a/tests/base_contract/conftest.py b/tests/base_contract/conftest.py index 486720fd..2a0adcc5 100644 --- a/tests/base_contract/conftest.py +++ b/tests/base_contract/conftest.py @@ -27,10 +27,10 @@ def anyio_backend(): @pytest.fixture -@patch.dict(ARTIFACTS_DICT, MOCK_ABI, clear=True) -@patch(WEB3, autospec=True) +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=True) def instance(mock_Web3): - with patch(WEB3) as 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() diff --git a/tests/base_contract/test_base_contract.py b/tests/base_contract/test_base_contract.py index 8a93adcf..88c23e2d 100644 --- a/tests/base_contract/test_base_contract.py +++ b/tests/base_contract/test_base_contract.py @@ -26,6 +26,7 @@ '31337': {'address': '0xcoordevnet'}}} } + def capture_args(*args, **kwargs) -> any: """Side effect function for checking args and kwargs passed""" return args, kwargs From 1820cb2d5cc1bead2d10c6d842f226b583f2d0c9 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 16 Mar 2021 03:09:53 -0400 Subject: [PATCH 10/14] Updated comments in test --- tests/base_contract/test_base_contract.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/base_contract/test_base_contract.py b/tests/base_contract/test_base_contract.py index 88c23e2d..9712f451 100644 --- a/tests/base_contract/test_base_contract.py +++ b/tests/base_contract/test_base_contract.py @@ -1,9 +1,11 @@ """ - This module tests the different possible branches of execution within the Zap base contract. + This module tests the different possible branches of execution within the Zap Base Contract. The following unit + tests require minimal dependencies. - The following unit tests require minimal dependencies. Because the base contract embodies the constructor role for - the entire Python interface of Zap (Zappy), the focus here lies in testing the instantiated dynamic attributes - of the base contract class. + 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 From 72a78a0b373cd98abeb48817eaed032186722c51 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 18 Mar 2021 02:37:57 -0400 Subject: [PATCH 11/14] Finished skeleton funcs up to event listeners --- src/dispatch/__init__.py | 0 src/dispatch/dispatch.py | 212 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 src/dispatch/__init__.py create mode 100644 src/dispatch/dispatch.py diff --git a/src/dispatch/__init__.py b/src/dispatch/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/dispatch/dispatch.py b/src/dispatch/dispatch.py new file mode 100644 index 00000000..77f06913 --- /dev/null +++ b/src/dispatch/dispatch.py @@ -0,0 +1,212 @@ +from typing import Optional, List, Dict +from src.base_contract.base_contract import BaseContract +import asyncio +from src.portedFiles.types import ( + Filter, address, TransactionCallback, + NetworkProviderOptions, const, txid +) +from web3 import Web3 + + +class ZapDispatch(BaseContract): + """ + NetworkProviderOptions -- Dictionary object containing options for BaseContract init + network_id -- Select which network the contract is located + options : (mainnet, testnet, private) + networkProvider -- Ethereum network provider (e.g. Infura or web3) + Example: + ZapDispatch({"networkId": 42, "networkProvider" : "web3"}) + """ + def __init__(self, options: NetworkProviderOptions = {}): + options["artifact_name"] = "DISPATCH" + BaseContract.__init__(self, **options) + + """Methods""" + + async def query_data(self, provider: address, query: str, endpoint: str, + endpoint_params: list[str], from_address: str, gas_price: Optional[int], gas: const.DEFAULT_GAS, + cb: TransactionCallback = None) -> txid: + """ + COMMENT HERE + + :param provider: + :param query: + :param endpoint: + :param endpoint_params: + :param from_address: + :param gas_price: + :param gas: + :param cb: + """ + try: + await asyncio.sleep(1) + if len(endpoint_params) > 0: + updated_params = [param if param.startswith('0x') + else param.encode('utf-8').hex() for param in endpoint_params] + else: + updated_params = endpoint_params + + tx_hash = self.contract.functions.query( + provider, query, Web3.toBytes(text=endpoint), updated_params + ).transact({'from': from_address, 'gas': gas, 'gasPrice': gas_price}) + + if cb: + cb(None, tx_hash) + return tx_hash.hex() + except Exception as e: + print(e) + + + async def cancel_query(self, query_id: str or int, + from_address: address, gas_price: Optional[int], + gas: const.DEFAULT_GAS) -> str or int: + """ + COMMENT HERE + + :param query_id: A unique identifier for the query. + :param from_address: + :param gas_price: + :param gas: + """ + try: + await asyncio.sleep(1) + self.contract.functions.cancelQuery(query_id).transact({'from': from_address, 'gas': gas, 'gasPrice': gas_price}) + except Exception as e: + return e # return 0? + else: + await asyncio.sleep(1) + 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], gas: const.DEFAULT_GAS): + """ + COMMENT HERE + + :param query_id: A unique identifier for the query. + :param response_params: + :param dynamic: + :param from_address: + :param gas_price: + :param gas: + """ + if dynamic is not False: + if type(response_params[0]) == int: + big_nums = 'I DONT THINK THIS APPLIES TO PYTHON. FIX ME' + self.contract.functions.respondIntArray(query_id, big_nums) + + return self.contract.methods.respondBytes32Array( + query_id, response_params).send({'from': from_address, 'gas': gas, 'gasPrice': gas_price}) + + p_length = len(response_params) + + if p_length == 1: + return self.contract.functions.respond1( + query_id, + response_params[0]).transact({'from': from_address, 'gas': gas, 'gasPrice': gas_price}) + + elif p_length == 2: + return self.contract.functions.respond2( + query_id, + response_params[0], + response_params[1]).transact({'from': from_address, 'gas': gas}) + + elif p_length == 3: + return 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: + return 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') + + """GETTERS""" + + async def get_query_id_provider(self, query_id: str or int) -> str: + """ + COMMENT HERE + + :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) -> str: + """ + COMMENT HERE + + :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: + """ + COMMENT HERE + + :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): + """ + COMMENT HERE + + :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): + """ + COMMENT HERE + + :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): + """ + COMMENT HERE + + :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: + """ + COMMENT HERE + + :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: + """ + COMMENT HERE + + :param filters: + :param cb: + """ + self.contract.events. + + + + + + From 4f4591e3864bd10d6d195cacc67ca25b60170725 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 6 Apr 2021 20:24:25 -0400 Subject: [PATCH 12/14] Finished Dispatch contract and testing --- src/dispatch/dispatch.py | 307 +++++++++++++++++++----------- tests/dispatch/__init__.py | 0 tests/dispatch/conftest.py | 132 +++++++++++++ tests/dispatch/test_dispatch.py | 324 ++++++++++++++++++++++++++++++++ 4 files changed, 657 insertions(+), 106 deletions(-) create mode 100644 tests/dispatch/__init__.py create mode 100644 tests/dispatch/conftest.py create mode 100644 tests/dispatch/test_dispatch.py diff --git a/src/dispatch/dispatch.py b/src/dispatch/dispatch.py index 77f06913..a2c561e9 100644 --- a/src/dispatch/dispatch.py +++ b/src/dispatch/dispatch.py @@ -1,212 +1,307 @@ -from typing import Optional, List, Dict -from src.base_contract.base_contract import BaseContract +from typing import Optional +from web3 import Web3 import asyncio -from src.portedFiles.types import ( + +from src.base_contract.base_contract import BaseContract +from src.types.types import ( Filter, address, TransactionCallback, NetworkProviderOptions, const, txid ) -from web3 import Web3 class ZapDispatch(BaseContract): """ - NetworkProviderOptions -- Dictionary object containing options for BaseContract init - network_id -- Select which network the contract is located + 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) - networkProvider -- Ethereum network provider (e.g. Infura or web3) + :param network_provider: Ethereum network provider (e.g. Infura or web3) Example: - ZapDispatch({"networkId": 42, "networkProvider" : "web3"}) + ZapDispatch({"network_id": 42, "network_provider" : 'web3'}) """ def __init__(self, options: NetworkProviderOptions = {}): options["artifact_name"] = "DISPATCH" - BaseContract.__init__(self, **options) - - """Methods""" - - async def query_data(self, provider: address, query: str, endpoint: str, - endpoint_params: list[str], from_address: str, gas_price: Optional[int], gas: const.DEFAULT_GAS, - cb: TransactionCallback = None) -> txid: + super().__init__(**options) + + ###### Methods ###### + + async def query_data(self, + provider: address, + query: str, + endpoint: str, + endpoint_params: list[str], + from_address: address, + gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS) -> txid: """ - COMMENT HERE - - :param provider: - :param query: - :param endpoint: - :param endpoint_params: - :param from_address: - :param gas_price: - :param gas: - :param cb: + 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. + :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(1) - if len(endpoint_params) > 0: - updated_params = [param if param.startswith('0x') - else param.encode('utf-8').hex() for param in endpoint_params] - else: - updated_params = endpoint_params + 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 = self.contract.functions.query( - provider, query, Web3.toBytes(text=endpoint), updated_params - ).transact({'from': from_address, 'gas': gas, 'gasPrice': gas_price}) + tx_hash = Web3.toHex(tx) + return tx_hash - if cb: - cb(None, tx_hash) - return tx_hash.hex() except Exception as e: print(e) - - async def cancel_query(self, query_id: str or int, - from_address: address, gas_price: Optional[int], - gas: const.DEFAULT_GAS) -> str or int: + async def cancel_query(self, + query_id: str or int, + from_address: address, + gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS) -> str or int: """ - COMMENT HERE + 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: - :param gas_price: - :param gas: + :param from_address: Address of the subscriber. + :param gas_price: Price per unit of gas. + :param gas: The gas limit of this transaction (optional). """ try: - await asyncio.sleep(1) - self.contract.functions.cancelQuery(query_id).transact({'from': from_address, 'gas': gas, 'gasPrice': gas_price}) - except Exception as e: - return e # return 0? + await asyncio.sleep(.5) + self.contract.functions.cancelQuery(query_id).transact( + {'from': from_address, 'gas': gas, 'gasPrice': gas_price} + ) + except ValueError: + return 0 + else: - await asyncio.sleep(1) 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], gas: const.DEFAULT_GAS): + async def respond(self, + query_id: str or int, + response_params: list[str], + dynamic: bool, + from_address: address, + gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS) -> txid: """ - COMMENT HERE + 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: - :param dynamic: - :param from_address: - :param gas_price: - :param gas: + :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. + :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: - big_nums = 'I DONT THINK THIS APPLIES TO PYTHON. FIX ME' - self.contract.functions.respondIntArray(query_id, big_nums) + 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 - return self.contract.methods.respondBytes32Array( - query_id, response_params).send({'from': from_address, 'gas': gas, 'gasPrice': gas_price}) + 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: - return self.contract.functions.respond1( - query_id, - response_params[0]).transact({'from': from_address, 'gas': gas, 'gasPrice': gas_price}) + tx = self.contract.functions.respond1( + query_id, response_params[0] + ).transact( + {'from': from_address, 'gas': gas, 'gasPrice': gas_price} + ) elif p_length == 2: - return self.contract.functions.respond2( - query_id, - response_params[0], - response_params[1]).transact({'from': from_address, 'gas': gas}) + tx = self.contract.functions.respond2( + query_id, response_params[0], response_params[1] + ).transact( + {'from': from_address, 'gas': gas} + ) elif p_length == 3: - return self.contract.functions.respond3( - query_id, - response_params[0], - response_params[1], - response_params[2]).transact({'from': from_address, 'gas': gas, 'gasPrice': gas_price}) + 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: - return 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}) + 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') - """GETTERS""" + tx_hash = Web3.toHex(tx) + return tx_hash + + ###### Getters ###### - async def get_query_id_provider(self, query_id: str or int) -> str: + async def get_query_id_provider(self, query_id: str or int) -> address: """ - COMMENT HERE + Fetches the provider of specified query id. :param query_id: A unique identifier for the query. """ - await asyncio.sleep(1) + await asyncio.sleep(.1) return self.contract.functions.getProvider(query_id).call() - async def get_subscriber(self, query_id: str or int) -> str: + async def get_subscriber(self, query_id: str or int) -> address: """ - COMMENT HERE + 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) + await asyncio.sleep(.1) return self.contract.functions.getSubscriber(query_id).call() async def get_endpoint(self, query_id: str or int) -> str: """ - COMMENT HERE + Fetches the endpoint of the query. :param query_id: A unique identifier for the query. """ - await asyncio.sleep(1) + 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): + async def get_status(self, query_id: str or int) -> int: """ - COMMENT HERE + Fetches the status of a query id. :param query_id: A unique identifier for the query. """ - await asyncio.sleep(1) + await asyncio.sleep(.1) return self.contract.functions.getStatus(query_id).call() - async def get_cancel(self, query_id: str or int): + async def get_cancel(self, query_id: str or int) -> int: """ - COMMENT HERE + Fetches the status of a cancelled query. :param query_id: A unique identifier for the query. """ - await asyncio.sleep(1) + await asyncio.sleep(.1) return self.contract.functions.getCancel(query_id).call() - async def get_user_query(self, query_id: str or int): + async def get_user_query(self, query_id: str or int) -> str: """ - COMMENT HERE + Fetches the user of the specified query id. :param query_id: A unique identifier for the query. """ - await asyncio.sleep(1) + await asyncio.sleep(.1) return self.contract.functions.getUserQuery(query_id).call() async def get_subscriber_onchain(self, query_id: str or int) -> bool: """ - COMMENT HERE + 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) + await asyncio.sleep(.1) return self.contract.functions.getSubscriberOnchain(query_id).call() - """EVENTS""" + ###### Events ###### - def listen(self, filters: Filter = {}, cb: TransactionCallback = None) -> None: + def listen(self, filters: Filter = {}, cb: TransactionCallback = None, ) -> None: """ - COMMENT HERE + Listens for all Dispatch contract events based on the optional filter. - :param filters: - :param cb: + :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: """ - self.contract.events. + 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/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..9ba76ff9 --- /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 cominations 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 integers 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 From 69b5e484b3a49c98827677973076c1fb66e735b6 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 6 Apr 2021 21:45:10 -0400 Subject: [PATCH 13/14] Fixed typo --- tests/dispatch/test_dispatch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/dispatch/test_dispatch.py b/tests/dispatch/test_dispatch.py index 9ba76ff9..207e628e 100644 --- a/tests/dispatch/test_dispatch.py +++ b/tests/dispatch/test_dispatch.py @@ -174,7 +174,7 @@ async def test_respond_dynamic_false(self, dispatch_contract, query_config, w3, [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 cominations of integers. + 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) @@ -201,7 +201,7 @@ async def test_response_dyanmic_int_array(self, dispatch_contract, query_config, [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 integers within the response parameters. + 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) From 243bbffc3fcd86c6051169262d11195f9f87863e Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 7 Apr 2021 13:56:14 -0400 Subject: [PATCH 14/14] Updated gas_price parameter to be optional and set default to current gas price --- src/dispatch/dispatch.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dispatch/dispatch.py b/src/dispatch/dispatch.py index a2c561e9..6e4acc4d 100644 --- a/src/dispatch/dispatch.py +++ b/src/dispatch/dispatch.py @@ -34,8 +34,8 @@ async def query_data(self, endpoint: str, endpoint_params: list[str], from_address: address, - gas_price: int, - gas: Optional[int] = const.DEFAULT_GAS) -> txid: + 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. @@ -45,7 +45,7 @@ async def query_data(self, :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. + :param gas_price: Price per unit of gas (optional). :param gas: The gas limit of this transaction (optional). """ if len(endpoint_params) > 0: @@ -70,7 +70,7 @@ async def query_data(self, async def cancel_query(self, query_id: str or int, from_address: address, - gas_price: int, + 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 @@ -78,7 +78,7 @@ async def cancel_query(self, :param query_id: A unique identifier for the query. :param from_address: Address of the subscriber. - :param gas_price: Price per unit of gas. + :param gas_price: Price per unit of gas (optional). :param gas: The gas limit of this transaction (optional). """ try: @@ -97,7 +97,7 @@ async def respond(self, response_params: list[str], dynamic: bool, from_address: address, - gas_price: int, + 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 @@ -107,7 +107,7 @@ async def respond(self, :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. + :param gas_price: Price per unit of gas (optional). :param gas: The gas limit of this transaction (optional). """ if dynamic is not False: