diff --git a/src/Artifacts/src/tests/test_artifacts.py b/src/Artifacts/src/tests/test_artifacts.py deleted file mode 100644 index c71d1c87..00000000 --- a/src/Artifacts/src/tests/test_artifacts.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest -import index - -class TestIndex: - - @pytest.fixture - def artifacts_iterator(self): - for i in index.Artifacts: - artifacts = index.Artifacts[i] - return artifacts - - def test_fetch_artifacts_by_key(self, artifacts_iterator): - assert type(artifacts_iterator) == dict - - with pytest.raises(AssertionError): - assert type(artifacts_iterator) != dict - - - def test_fetch_arbiter_address(self): - arbiter_addr = index.Artifacts['ARBITER']['networks']['1']['address'] - assert arbiter_addr == '0x131e22ae3e90f0eeb1fb739eaa62ea0290c3fbe1' - - def test_fetch_zap_coor_kovan_address(self): - zap_coor_kovan_addr = index.Artifacts['ZAPCOORDINATOR']['networks']['42']['address'] - assert zap_coor_kovan_addr == '0xdbcac7c8bcca78fb05e96d0d2c68efb1c5922539' - - def test_fetch_registry_devnet_address(self): - registry_devnet_addr = index.Artifacts['REGISTRY']['networks']['31337']['address'] - assert registry_devnet_addr == '0xa513E6E4b8f2a923D98304ec87F64353C4D5C853' - - - def test_zap_coor_abi(self): - zap_get_contract = index.Artifacts['ZAPCOORDINATOR']['abi'] - assert type(zap_get_contract) == list \ No newline at end of file diff --git a/src/BaseContract/base_contract.py b/src/BaseContract/base_contract.py deleted file mode 100644 index fde613e6..00000000 --- a/src/BaseContract/base_contract.py +++ /dev/null @@ -1,24 +0,0 @@ -import utils -from web3 import Web3 - -class BaseContract: - def __init__(self, artifact_name, network_id, network_provider, coordinator, address, web3): - self.name = artifact_name - try: - self.artifact = artifact_name - self.network_id = network_id or '1' - coor_artifact_abi = utils.load_abi('ZapCoordinator') - coor_artifact_address = utils.load_address('ZapCoordinator', self.network_id) - self.provider = web3 or Web3(network_provider) or Web3(Web3.HTTPProvider('https://cloudflare-eth.com')) - self.coordinator = self.provider.eth.Contract(coor_artifact_abi, coordinator or coor_artifact_address) - self.contract = None - if address: - self.address = address - else: - self.address = self.artifact.networks[self.network_id].address - if coordinator: - self.coordinator.getContract() - else: - self.coordinator = self.provider.eth.Contract(self.artifact.abi, self.address) - except Exception as e: - raise e \ No newline at end of file diff --git a/src/BaseContract/test/test_base_contract.py b/src/BaseContract/test/test_base_contract.py deleted file mode 100644 index e32a0fbe..00000000 --- a/src/BaseContract/test/test_base_contract.py +++ /dev/null @@ -1,9 +0,0 @@ -# from unittest import TestCase -# from unittest.mock import Mock, MagicMock, patch -# import base_contract -# import utils - - -# class test_base_contract(TestCase): -# pass - diff --git a/src/BaseContract/utils.py b/src/BaseContract/utils.py deleted file mode 100644 index 6244e404..00000000 --- a/src/BaseContract/utils.py +++ /dev/null @@ -1,29 +0,0 @@ -import json -import os - - -# These functions replace the monorepo's artifactDir and negates the need for each -# artifact's location to be hardcoded in a seperate module. -# -# Helper function for load_abi and load_address. -def load_json(name: str) -> str: - # Adjust path below to fetch Artifacts/.json. - path = "./Artifacts/" - with open(os.path.abspath(path + f"{name}.json")) as f: - json_file = json.load(f) - return json_file - - -def load_abi(name: str) -> dict: - obj = load_json(name) - abi = obj['abi'] - return abi - -def load_address(name: str, netId: int or str) -> dict: - try: - a_dict = load_json(name) - addr = a_dict['networks'] - network_address = addr[netId]['address'] - return network_address - except Exception as e: - print('Error with: ' + str(e)) diff --git a/src/Artifacts/contracts/Arbiter.json b/src/artifacts/contracts/Arbiter.json similarity index 100% rename from src/Artifacts/contracts/Arbiter.json rename to src/artifacts/contracts/Arbiter.json diff --git a/src/Artifacts/contracts/Bondage.json b/src/artifacts/contracts/Bondage.json similarity index 100% rename from src/Artifacts/contracts/Bondage.json rename to src/artifacts/contracts/Bondage.json diff --git a/src/Artifacts/contracts/Client1.json b/src/artifacts/contracts/Client1.json similarity index 100% rename from src/Artifacts/contracts/Client1.json rename to src/artifacts/contracts/Client1.json diff --git a/src/Artifacts/contracts/Client2.json b/src/artifacts/contracts/Client2.json similarity index 100% rename from src/Artifacts/contracts/Client2.json rename to src/artifacts/contracts/Client2.json diff --git a/src/Artifacts/contracts/Client3.json b/src/artifacts/contracts/Client3.json similarity index 100% rename from src/Artifacts/contracts/Client3.json rename to src/artifacts/contracts/Client3.json diff --git a/src/Artifacts/contracts/Client4.json b/src/artifacts/contracts/Client4.json similarity index 100% rename from src/Artifacts/contracts/Client4.json rename to src/artifacts/contracts/Client4.json diff --git a/src/Artifacts/contracts/ClientBytes32Array.json b/src/artifacts/contracts/ClientBytes32Array.json similarity index 100% rename from src/Artifacts/contracts/ClientBytes32Array.json rename to src/artifacts/contracts/ClientBytes32Array.json diff --git a/src/Artifacts/contracts/ClientIntArray.json b/src/artifacts/contracts/ClientIntArray.json similarity index 100% rename from src/Artifacts/contracts/ClientIntArray.json rename to src/artifacts/contracts/ClientIntArray.json diff --git a/src/Artifacts/contracts/CurrentCost.json b/src/artifacts/contracts/CurrentCost.json similarity index 100% rename from src/Artifacts/contracts/CurrentCost.json rename to src/artifacts/contracts/CurrentCost.json diff --git a/src/Artifacts/contracts/Dispatch.json b/src/artifacts/contracts/Dispatch.json similarity index 100% rename from src/Artifacts/contracts/Dispatch.json rename to src/artifacts/contracts/Dispatch.json diff --git a/src/Artifacts/contracts/Piecewise.json b/src/artifacts/contracts/Piecewise.json similarity index 100% rename from src/Artifacts/contracts/Piecewise.json rename to src/artifacts/contracts/Piecewise.json diff --git a/src/Artifacts/contracts/Registry.json b/src/artifacts/contracts/Registry.json similarity index 100% rename from src/Artifacts/contracts/Registry.json rename to src/artifacts/contracts/Registry.json diff --git a/src/Artifacts/contracts/RegistryInterface.json b/src/artifacts/contracts/RegistryInterface.json similarity index 100% rename from src/Artifacts/contracts/RegistryInterface.json rename to src/artifacts/contracts/RegistryInterface.json diff --git a/src/Artifacts/contracts/TokenDotFactory.json b/src/artifacts/contracts/TokenDotFactory.json similarity index 100% rename from src/Artifacts/contracts/TokenDotFactory.json rename to src/artifacts/contracts/TokenDotFactory.json diff --git a/src/Artifacts/contracts/TokenFactory.json b/src/artifacts/contracts/TokenFactory.json similarity index 100% rename from src/Artifacts/contracts/TokenFactory.json rename to src/artifacts/contracts/TokenFactory.json diff --git a/src/Artifacts/contracts/ZapCoordinator.json b/src/artifacts/contracts/ZapCoordinator.json similarity index 100% rename from src/Artifacts/contracts/ZapCoordinator.json rename to src/artifacts/contracts/ZapCoordinator.json diff --git a/src/Artifacts/contracts/ZapCoordinatorInterface.json b/src/artifacts/contracts/ZapCoordinatorInterface.json similarity index 100% rename from src/Artifacts/contracts/ZapCoordinatorInterface.json rename to src/artifacts/contracts/ZapCoordinatorInterface.json diff --git a/src/Artifacts/contracts/ZapToken.json b/src/artifacts/contracts/ZapToken.json similarity index 100% rename from src/Artifacts/contracts/ZapToken.json rename to src/artifacts/contracts/ZapToken.json diff --git a/src/Artifacts/src/tests/__init__.py b/src/artifacts/src/__init__.py similarity index 100% rename from src/Artifacts/src/tests/__init__.py rename to src/artifacts/src/__init__.py diff --git a/src/Artifacts/src/index.py b/src/artifacts/src/index.py similarity index 100% rename from src/Artifacts/src/index.py rename to src/artifacts/src/index.py diff --git a/src/base_contract/__init__.py b/src/base_contract/__init__.py new file mode 100644 index 00000000..c54d3f94 --- /dev/null +++ b/src/base_contract/__init__.py @@ -0,0 +1,2 @@ +from web3 import Web3 +from src.artifacts.src.index import Artifacts \ No newline at end of file diff --git a/src/base_contract/base_contract.py b/src/base_contract/base_contract.py new file mode 100644 index 00000000..07dbb55a --- /dev/null +++ b/src/base_contract/base_contract.py @@ -0,0 +1,102 @@ +from web3 import Web3 +import asyncio + +import src.artifacts.src.index as index +import src.base_contract.utils as utils + + +class BaseContract: + provider: any + web3: any + contract: any + network_id: int + coordinator: any + artifact: any + name: str + address: str or None + + def __init__(self, + artifact_name: str, + web3: any = None, + network_id: int = 1, + network_provider: any = None, + artifacts_dir: str = None, + coordinator: str = None, + address: str = None + ): + try: + if artifacts_dir is None: + self.artifact = index.Artifacts[artifact_name] + self.coor_artifact = index.Artifacts['ZAPCOORDINATOR'] + else: + artifacts: any = utils.Utils.get_artifacts(artifacts_dir) + self.artifact = utils.Utils.open_artifact_in_dir(artifacts[artifact_name]) + self.coor_artifact = utils.Utils.open_artifact_in_dir(artifacts['ZAPCOORDINATOR']) + + self.name = artifact_name + self.provider = web3 or Web3(network_provider or Web3.HTTPProvider("https://cloudflare-eth.com")) + self.w3 = web3 or Web3(network_provider or Web3.HTTPProvider("https://cloudflare-eth.com")) + self.network_id = network_id or 1 + self.address = address or self.artifact['networks'][str(self.network_id)]['address'] + + if coordinator is not None: + self.coordinator = self.w3.eth.contract(address=self.w3.toChecksumAddress(coordinator), + abi=self.coor_artifact['abi']) + + contract_address = self.get_contract() + self.contract = self.w3.eth.contract(address=self.w3.toChecksumAddress(contract_address), + abi=self.artifact['abi']) + + else: + self.coor_address = self.coor_artifact['networks'][str(self.network_id)]['address'] + self.coordinator = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.coor_address), + abi=self.coor_artifact['abi']) + + self.contract = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.address), + abi=self.artifact['abi']) + + except Exception as e: + raise e + + async def _get_contract(self) -> str: + """ + This async function fetches the contract address from the coordinator abi. Thereafter, the function returns the + address where it is assigned to the address kwarg in the contract instance. + + :return: the contract address of the coordinator. + """ + await asyncio.sleep(.5) + contract_address = self.coordinator.functions.getContract(self.name).call() + return contract_address + + async def _get_contract_owner(self) -> str: + """ + This async function fetches the owner of the contract instance. + + :return: the contract owner's address. + """ + await asyncio.sleep(.5) + contract_owner = self.contract.functions.owner().call() + return contract_owner + + def get_contract(self) -> str: + """ + A synchronous function that wraps the asynchronous _get_contract method. This function returns the contract + address fetched from the asynchronous _get_contract function. + + :return: the contract address. + """ + task = self._get_contract() + contract_address = asyncio.run(task) + return contract_address + + def get_contract_owner(self) -> str: + """ + A synchronous function that wraps the asynchronous _get_contract_owner method. This function returns the + contract owner's address. + + :return: the contract owner's address. + """ + task = self._get_contract_owner() + owner = asyncio.run(task) + return owner diff --git a/src/base_contract/utils.py b/src/base_contract/utils.py new file mode 100644 index 00000000..45ef1ec7 --- /dev/null +++ b/src/base_contract/utils.py @@ -0,0 +1,41 @@ +import os +import json + + +class Utils: + + """ + # @ignore + # @params {string} buildDir + # @returns {any} + """ + + @staticmethod + def get_artifacts(build_dir: str) -> dict: + artifacts = { + 'ARBITER': os.path.join(build_dir, 'Arbiter.json'), + 'BONDAGE': os.path.join(build_dir, 'Bondage.json'), + 'DISPATCH': os.path.join(build_dir, 'Dispatch.json'), + 'REGISTRY': os.path.join(build_dir, 'Registry.json'), + 'CurrentCost': os.path.join(build_dir, 'CurrentCost.json'), + 'PiecewiseLogic': os.path.join(build_dir, 'PiecewiseLogic.json'), + 'ZAP_TOKEN': os.path.join(build_dir, 'ZapToken.json'), + 'Client1': os.path.join(build_dir, 'Client1.json'), + 'Client2': os.path.join(build_dir, 'Client2.json'), + 'Client3': os.path.join(build_dir, 'Client3.json'), + 'Client4': os.path.join(build_dir, 'Client4.json'), + 'ZAPCOORDINATOR': os.path.join(build_dir, 'ZapCoordinator.json'), + 'TOKENDOTFACTORY': os.path.join(build_dir, 'TokenDotFactory.json'), + } + return artifacts + + """ + # @ignore + # @params {string} artifact + # @returns {dict} + """ + @staticmethod + def open_artifact_in_dir(artifact: str) -> dict: + with open(artifact) as f: + abi = json.load(f) + return abi diff --git a/src/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/BaseContract/test/__init__.py b/tests/base_contract/__init__.py similarity index 100% rename from src/BaseContract/test/__init__.py rename to tests/base_contract/__init__.py diff --git a/tests/base_contract/conftest.py b/tests/base_contract/conftest.py new file mode 100644 index 00000000..2a0adcc5 --- /dev/null +++ b/tests/base_contract/conftest.py @@ -0,0 +1,37 @@ +from unittest.mock import MagicMock, patch +import pytest +import re +import src.base_contract.base_contract as base_contract + +"""Default setup""" + +MOCK_ABI = { + 'TEST_ARTIFACT': {'abi': [], 'networks': + {'1': {'address': '0xmainnet'}, + '42': {'address': '0xkovan'}, + '31337': {'address': '0xdevnet'}}}, + 'ZAPCOORDINATOR': {'abi': [], 'networks': + {'1': {'address': '0xcoormainnet'}, + '42': {'address': '0xcoorkovan'}, + '31337': {'address': '0xcoordevnet'}}} +} + + +@pytest.fixture +def anyio_backend(): + """ + Ensures anyio uses the default, pytest-asyncio plugin + for running async tests. + """ + return 'asyncio' + + +@pytest.fixture +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=True) +def instance(mock_Web3): + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + return base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) diff --git a/tests/base_contract/test_base_contract.py b/tests/base_contract/test_base_contract.py new file mode 100644 index 00000000..9712f451 --- /dev/null +++ b/tests/base_contract/test_base_contract.py @@ -0,0 +1,477 @@ +""" + This module tests the different possible branches of execution within the Zap Base Contract. The following unit + tests require minimal dependencies. + + The Base Contract is the parent class to the Dispatch, Bondage, Arbiter, Token, and Registry classes; further, it + provides access to both the contract instance as well as the Web3 provider instance. Because the majority of this + contract is a constructor, the focus here lies in testing the instantiated dynamic attributes of the base contract + class. +""" + +from unittest.mock import MagicMock, patch +import pytest +import re +import src.base_contract.base_contract as base_contract + + +"""Default setup""" + + +MOCK_ABI = { + 'TEST_ARTIFACT': {'abi': [], 'networks': + {'1': {'address': '0xmainnet'}, + '42': {'address': '0xkovan'}, + '31337': {'address': '0xdevnet'}}}, + 'ZAPCOORDINATOR': {'abi': [], 'networks': + {'1': {'address': '0xcoormainnet'}, + '42': {'address': '0xcoorkovan'}, + '31337': {'address': '0xcoordevnet'}}} +} + + +def capture_args(*args, **kwargs) -> any: + """Side effect function for checking args and kwargs passed""" + return args, kwargs + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=True) +class TestInit: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + + def test_instance_name(self, mock_Web3, instance): + """ + Sanity check to ensure the instance runs without errors while mocking web3. + """ + assert type(instance.name) == str + assert instance.name == 'TEST_ARTIFACT' + + with pytest.raises(AssertionError): + assert type(instance.name) != str + assert instance.name != 'TEST_ARTIFACT' + + def test_name_without_arg(self, mock_Web3): + """ + Testing that the contract fails if the artifact_name kwarg is an empty string. + """ + with pytest.raises(KeyError): + instance = base_contract.BaseContract(artifact_name='', web3=self.w3) + assert instance + + def test_network_id_default(self, mock_Web3, instance): + """ + Testing that the default network (1) is assigned to the contract instance. + """ + assert type(instance.network_id) == int + assert instance.network_id == 1 + + with pytest.raises(AssertionError): + assert type(instance.network_id) != int + assert instance.network_id != 1 + + @pytest.mark.parametrize('input', [1, 42, 31337]) + def test_assigned_network_ids(self, mock_Web3, input): + """ + Testing that the network kwarg gets assigned to the contract network instance. + + :param mock_Web3: patched web3. + :param input: the relevant networks' corresponding integer. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input, web3=self.w3) + + assert instance.network_id == input + + with pytest.raises(AssertionError): + assert instance.network_id != input + + @pytest.mark.parametrize('wrong_net_id', [11, 16, 47, 118893, 'string']) + def test_network_id_should_fail_if_given_unknown_id(self, mock_Web3, wrong_net_id): + """ + Testing that the contract fails if given an unknown network id. + """ + with pytest.raises(KeyError): + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=wrong_net_id, web3=self.w3) + assert instance + + def test_network_id_of_zero(self, mock_Web3): + """ + Testing that the network_id of zero actually uses the default network of '1.' + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=0, web3=self.w3) + + assert instance.network_id != 0 + assert instance.network_id == 1 + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=False) +class TestAddress: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + + def test_address_argument(self, mock_Web3): + """ + Testing that the address argument is assigned as the instance address. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', address='0x_some_address', web3=self.w3) + + assert type(instance.address) == str + assert instance.address == '0x_some_address' + + with pytest.raises(AssertionError): + assert instance.address == '0x_some_other_address' + + @pytest.mark.parametrize('network_input, address_output', [(1, '0xmainnet'), (42, '0xkovan'), (31337, '0xdevnet')]) + def test_address_with_no_argument(self, mock_Web3, network_input, address_output): + """ + Testing that the address is correctly fetched from the abi when no address arg is given. + + :param mock_Web3: patched web3. + :param network_input: relevant networks including mainnet, kovan, and devnet. + :param address_output: the associated address within the MOCK_ABI dictionary. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=network_input, web3=self.w3) + + assert type(instance.address) == str + assert instance.address == address_output + + with pytest.raises(AssertionError): + assert instance.address == '0x_wrong_address' + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=True) +class TestArtifacts: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + + def test_artifact_and_coor_artifact_assignments_from_dictionary(self, mock_Web3, instance): + """ + Testing that the artifact_name kwarg triggers the artifacts dictionary and populates the artifact + attribute with the controlled mock abi. + + :param mock_Web3: patched web3. + """ + assert type(instance.artifact) == dict + assert instance.artifact['networks']['1']['address'] == '0xmainnet' + + assert type(instance.coor_artifact) == dict + assert instance.coor_artifact['networks']['1']['address'] == '0xcoormainnet' + + with pytest.raises(AssertionError): + assert type(instance.artifact) != dict + assert instance.artifact['networks']['1']['address'] == '0x123' + + assert type(instance.coor_artifact) != dict + assert instance.coor_artifact['networks']['1']['address'] == '0x123' + + +@patch('src.base_contract.Web3', autospec=False) +class TestArtifactsDirectory: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = MagicMock() + + mock_test_abi = {'abi': [], 'networks': {'1': {'address': '0xmainnet'}, '42': {'address': '0xkovan'}, + '31337': {'address': '0xdevnet'}}} + + mock_coor_abi = {'abi': [], 'networks': {'1': {'address': '0xcoormainnet'}, '42': {'address': '0xcoorkovan'}, + '31337': {'address': '0xcoordevnet'}}} + + mock_dict_dir = {'TEST_ARTIFACT': 'artifacts/contracts/TestArtifact.json', + 'ZAPCOORDINATOR': 'artifacts/contracts/ZapCoordinator.json'} + + @patch('src.base_contract.utils.Utils.get_artifacts') + @patch('src.base_contract.utils.Utils.open_artifact_in_dir') + @pytest.mark.parametrize( + 'art_input, net_id_input, address_output, coor_address_output', [ + ('TEST_ARTIFACT', 1, '0xmainnet', '0xcoormainnet'), ('TEST_ARTIFACT', 42, '0xkovan', '0xcoorkovan'), + ('TEST_ARTIFACT', 31337, '0xdevnet', '0xcoordevnet') + ]) + def test_artifact_instance_from_artifacts_directory_arg(self, mock_utils_abi, mock_artifact_dir, mock_Web3, + art_input, net_id_input, address_output, + coor_address_output): + """ + Testing that the artifacts_dir kwarg passes through both get_artifacts and open_artifact_in_dir functions + respectively. Thereafter, the base_contract instance attribute 'artifact' should be assigned as an object + (abi). The get_artifacts and open_artifact_in_dir functions are patched and return expected values. + + :param mock_utils_abi: a mocked abi that mimics what open_artifacts_in_dir function will return. + :param mock_artifact_dir: a small mocked directory that replicates the return of get_artifacts function. + :param mock_Web3: patched web3. + """ + + """Mock the relevant function returns""" + mock_artifact_dir.return_value = self.mock_dict_dir + mock_utils_abi.side_effect = [self.mock_test_abi, self.mock_coor_abi] + + instance = base_contract.BaseContract(artifact_name=art_input, artifacts_dir='some/path/', + network_id=net_id_input, web3=self.w3) + + assert type(instance.artifact) == dict + assert type(instance.artifact['abi']) == list + assert instance.artifact['networks'][str(net_id_input)]['address'] == address_output + + """Checking coordinator instance object and coordinator instance address are also set""" + + assert type(instance.coor_address) == str + assert type(instance.coor_artifact['abi']) == list + assert instance.coor_address == coor_address_output + + """Assertions regarding the artifact instance that should fail""" + + with pytest.raises(AssertionError): + assert type(instance.artifact) == list + assert type(instance.artifact['abi']) == dict + assert instance.artifact['network'][str(net_id_input)]['address'] == '0xfailingstring' + assert instance.artifact['abi']['network'] + + """Assertions regarding the coordinator artifact instance that should fail""" + + assert type(instance.coor_address) != str + assert type(instance.coor_artifact['abi']) != dict + assert instance.coor_address == '0xfailingstring' + assert instance.coor_artifact['abi']['network'] + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=False) +class TestContracts: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.toChecksumAddress.side_effect = capture_args + w3.eth.contract.side_effect = capture_args + + @patch('src.base_contract.base_contract.BaseContract.get_contract') + @pytest.mark.parametrize('net_id', [1, 42, 31337]) + def test_coordinator_contract_args_with_coordinator_address_provided(self, mock_get_contract, mock_Web3, net_id): + """ + Testing that the coordinator contract object is assigned with the correct args. This test first mocks the + get_contract function so the contract instance runs without errors. + """ + mock_get_contract = MagicMock() + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='0x_some_address', + network_id=net_id, web3=self.w3) + + assert type(instance.coordinator) == tuple + assert instance.coordinator[1]['abi'] == [] + + """ + Iterate through the returned tuple to find the passed address. Because of all the mocks, the returned + kwargs include a lot of unnecessary punctuation--hence the use of regexp. + """ + + expected_address = '0x_some_address' + + res = re.search(expected_address, str(instance.coordinator[1]['address'])) + assert res is not None + + with pytest.raises(AssertionError): + res = re.search('this_should_fail', str(instance.coordinator[1]['address'])) + assert res is not None + + @pytest.mark.parametrize('input_id, expected_output', [(1, '0xcoormainnet'), (42, '0xcoorkovan'), + (31337, '0xcoordevnet')]) + def test_coordinator_contract_without_coordinator_address_provided(self, mock_Web3, input_id, expected_output): + """ + Testing that the coordinator contract object is assigned with the correct args. The first assertions test that + the coordinator instance was correctly assigned. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id, web3=self.w3) + + assert type(instance.coordinator) == tuple + assert instance.coordinator[1]['abi'] == [] + + res = re.search(expected_output, str(instance.coordinator[1]['address'])) + assert res is not None + + with pytest.raises(AssertionError): + res = re.search('this_should_fail', str(instance.coordinator[1]['address'])) + assert res is not None + + @patch('src.base_contract.base_contract.BaseContract.get_contract') + def test_contract_instance_with_coor_through_get_contract_method(self, mock_get_contract, mock_Web3): + """ + Testing that the return value from get_contract passes through to the contract instance object. This test + mocks the get_contract function to return the expected value. + """ + test_address = '0x_some_address' + mock_get_contract.return_value = test_address + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator=test_address, web3=self.w3) + + res = re.search(test_address, str(instance.contract[1]['address'])) + assert res is not None + + @pytest.mark.parametrize('input_id, expected_address', [(1, '0xmainnet'), (42, '0xkovan'), (31337, '0xdevnet')]) + def test_contract_instance_if_no_coor_provided(self, mock_Web3, input_id, expected_address): + """ + Testing that the contract instance object is assigned with the correct args using the MOCK_ABI with + different networks. + """ + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', network_id=input_id, web3=self.w3) + + assert type(instance.contract) == tuple + assert instance.contract[1]['abi'] == [] + + expected_address = expected_address + res = re.search(expected_address, str(instance.contract[1]['address'])) + assert res is not None + + with pytest.raises(AssertionError): + res = re.search('this_will_fail', str(instance.contract[1]['address'])) + assert res is not None + + +@patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True) +@patch('src.base_contract.Web3', autospec=False) +class TestWrapperMethods: + + """Setup""" + + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.toChecksumAddress.side_effect = capture_args + w3.eth.contract.side_effect = capture_args + + @patch('src.base_contract.base_contract.BaseContract._get_contract') + def test_sync_get_contract(self, mock_async_get_contract, mock_Web3): + """ + Testing that the get_contract function returns the proper values from the _get_contract async function. This + test mocks the async _get_contract function and gives it a return value to check that the get_contract + wrapper returns the proper value. + """ + mock_async_get_contract.return_value = 'test_get_contract_address' + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', coordinator='some_address', web3=self.w3) + + assert type(instance.get_contract()) == str + assert instance.get_contract() == 'test_get_contract_address' + + """Test false positive""" + + with pytest.raises(AssertionError): + assert instance.get_contract() == 'this_should_fail' + + + @patch('src.base_contract.base_contract.BaseContract._get_contract_owner') + def test_sync_get_contract_owner(self, mock_async_owner, mock_Web3, instance): + """ + Testing that the get_contract_owner returns the proper values from the async _get_contract_owner function. + The test mocks the async _get_contract_owner function and gives it a return value to check that the + get_contract_owner wrapper returns the proper value. + """ + mock_async_owner.return_value = 'test_owner_address' + + assert type(instance.get_contract_owner()) == str + assert instance.get_contract_owner() == 'test_owner_address' + + with pytest.raises(AssertionError): + assert instance.get_contract_owner() == 'this_should_fail' + + +class TestAsyncMethods: + + """ + The following asynchronous functions should not be called directly; rather, the wrapper function should be + called instead. The following unit tests require the somewhat hacky functions mock_owner_abi and mock_coor_abi + in order to ensure the async functions were called. The mock abi functions are returned as a side effect from + the web3.eth.contract call. While patching both Web3 and the artifacts dictionary, these functions proved + difficult to test (hence, the creative workaround). The Pythonic syntax of these side effects are, nevertheless, + exactly the same: + _get_contract_owner() = .functions.owner().call() + _get_contract() = .functions.getContract(self.name).call() + """ + + """Setup""" + + + def mock_owner_abi(self, *args, **kwargs): + """Function mimicking abi""" + return_val = '0x_owner_address' + class mimicked_abi: + class functions: + class owner: + def call(self): + return return_val + return mimicked_abi + + + def mock_coor_abi(self, *args, **kwargs): + """Function mimicking abi""" + return_val = '0x_artifact_address' + + class mimicked_coordinator: + class functions: + class getContract: + def __init__(self, name): + self.name = name + def call(self): + return return_val + return mimicked_coordinator + + @pytest.mark.anyio + async def test_async_get_contract_owner(self): + """ + Testing the asynchronous _get_contract_owner function. + """ + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = self.mock_owner_abi + w3.toChecksumAddress.side_effect = MagicMock() + + with patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True): + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) + res = await instance._get_contract_owner() + assert res == '0x_owner_address' + + with pytest.raises(AssertionError): + assert res == 'this_should_fail' + + @pytest.mark.anyio + async def test_async_get_contract(self): + """ + Testing the asynchronous _get_contract function. + """ + with patch('src.base_contract.Web3') as mock_Web3: + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + w3.eth.contract.side_effect = self.mock_coor_abi + w3.toChecksumAddress.side_effect = MagicMock() + + with patch.dict(base_contract.index.Artifacts, MOCK_ABI, clear=True): + + instance = base_contract.BaseContract(artifact_name='TEST_ARTIFACT', web3=w3) + res = await instance._get_contract() + assert res == '0x_artifact_address' + + with pytest.raises(AssertionError): + assert res == 'this_should_fail'