From 5740b168b941aa914731b9322ba3dbbeb4ce532d Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 1 Mar 2021 11:21:27 -0400 Subject: [PATCH 01/20] Added Registry; Added tests Readme --- src/Registry/__init__.py | 0 src/Registry/registry.py | 0 tests/README.md | 18 ++++++++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 src/Registry/__init__.py create mode 100644 src/Registry/registry.py create mode 100644 tests/README.md diff --git a/src/Registry/__init__.py b/src/Registry/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/Registry/registry.py b/src/Registry/registry.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..3b64e74f --- /dev/null +++ b/tests/README.md @@ -0,0 +1,18 @@ +# How to test + +## Prerequisites +```bash +# go to zappy root +cd path/to/zappy_root/ +# activate virtual environment +source ./venv/bin/activate +# install pytest with pip +pip3 install pytest +``` +## Usage +```bash +pytest -v +``` +The `-v` option will give you a detailed output of tests. + +For extended usage documentation, take a look at the [pytest docs](https://docs.pytest.org/en/stable/usage.html). From 5f3129120f840a5852ddfbb6b004a9be6465bdd0 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Thu, 4 Mar 2021 13:34:28 -0400 Subject: [PATCH 02/20] Registry Contract Draft, pending tests; moved zaptypes (types.py) to `src` folder --- src/Registry/registry.py | 281 +++++++++++++++++++++++++++++++++++++++ src/Types/types.py | 220 ++++++++++++++++++++++++++++++ 2 files changed, 501 insertions(+) create mode 100644 src/Types/types.py diff --git a/src/Registry/registry.py b/src/Registry/registry.py index e69de29b..e1216e8a 100644 --- a/src/Registry/registry.py +++ b/src/Registry/registry.py @@ -0,0 +1,281 @@ +from web3.Web3 import toHex, toText, toBytes + +from BaseContract.base_contract import BaseContract +from ZapToken.Curve.curve import Curve +from Types.types import ( + Filter, address, + NetworkProviderOptions, const, TransactionCallback +) + + +class ZapRegistry(BaseContract): + """This contract manages Providers and Curve registration + + NetworkProviderOptions -- Dictionary object containing options for + BaseContract init + + NetworkProviderOptions has the following keyword arguments: + + arifactsDir -- Directory where contract ABIs are located + networkId -- Select which network the contract is located + options : (mainnet, testnet, private) + networkProvider -- Ethereum network provider (e.g. Infura or web3) + + Example: + ZapRegistry({"networkId": 42, "networkProvider": "web3"}) + """ + + def __init__(self, options: NetworkProviderOptions): + options["artifactName"] = "REGISTRY" + super().__init__(**options) + + """ + Registry storage calls for all providers + """ + + # async def task(func, action, args1=[], args2=[]): + # item = func(*args1).action(*args2) + # asyncio.sleep(5) + + # if item: + # return item + # return + + async def get_all_providers(self): + """Get all providers in Registry Contract. + + returns a list of oracles once async is fulfilled + """ + await self.contract.functions.getAllOracles().call() + + async def get_provider_address_by_index(self, index: int): + """ Look up provider's address by its index in registry storage + + returns address of indexed provider once async is fulfilled + """ + await self.contract.functions.getOracleAddress(index).call() + + """ + Provider specific calls + """ + + async def initiate_provider(self, public_key: str, title: str, + From: address, cb: TransactionCallback, + gas=const.DEFAULT_GAS): + """ + Initiates a brand endpoint in the Registry contract, + creating an Oracle entry if need be. + + Arguments: + public_key -- a public identifier for this oracle + title -- describes what data this oracle provides + from -- Ethereum address of the account that is + initializing this provider + gas -- Sets the fas limit for this transaction. + Defaults to 4 * 10**5 + + """ + + try: + tx_hash = await self.contract.functions.InitiateProvider( + str(public_key), toHex(title)).transact( + {"from": From, "gas": gas}) + + cb(None, tx_hash) + except Exception as e: + cb(e) + + return tx_hash + + async def get_provider_publickey(self, provider: address): + """ Get a provider's public key from the registry contract. + + provider -- The address of this provider + + returns the public key number. + """ + pub_key: str = await\ + self.contract.functions.getProviderPublicKey(provider).call() + + return pub_key + + async def get_providertitle(self, provider: address): + title = await\ + self.contract.functions.getProviderTitle(provider).call() + + return toText(title) + + async def set_provider_title(self, From: address, title: str, + cb: TransactionCallback, gas=const.DEFAULT_GAS): + """ Set the new provider's title + + Arguments: + From -- The address of this provider + title -- The new title of this provider + cb -- Callback for transactionHash event + + """ + try: + tx_hash = await\ + self.contract.functions.setProviderTitle(toHex(title)).transact( + {"from": From, "gas": gas}) + cb(None, tx_hash) + + except Exception as e: + cb(e, None) + + return tx_hash + + async def is_provider_initiated(self, provider: address): + return await self.contract.is_provider_initiated(provider) + + async def get_provider_param(self, provider: address, key: str): + return await self.contract.functions.getProviderParameter(provider, toHex(key)) + + async def get_all_provider_params(self, provider: address): + return await self.contract.functions.getAllProviderParams(provider).call() + + async def get_provider_endpoints(self, provider: address): + endpoints = await\ + self.contract.functions.getProviderEndpoints(provider).call() + endpoints = [toText(endpoint) for endpoint in endpoints] + valid_endpoints = [e for e in endpoints if e != ''] + + return valid_endpoints + + """ + Provider's specific endpoint calls + """ + + async def initiate_provider_curve(self, end_point, term, + From, gasPrice, cb, + broker=const.NULL_ADDRESS, + gas=const.DEFAULT_GAS): + """""" + hex_terms = [toHex(t) for t in term] + + try: + tx_hash = await\ + self.contract.functions.initiateProviderCurve(toHex(end_point), + hex_terms, broker) + cb(None, tx_hash) + except Exception as e: + cb(e) + + return tx_hash + + async def clear_endpoint(self, endpoint, From, gasPrice, + cb: TransactionCallback, gas=const.DEFAULT_GAS): + try: + tx_hash = await\ + self.contract.functions.clearEndpoint(toHex(endpoint)).send( + {"from": From, "gas": gas}) + cb(None, tx_hash) + except Exception as e: + cb(e) + + return tx_hash + + async def get_provider_curve(self, provider: address, endpoint: str): + terms: list = await\ + self.contract.functions.getProviderCurve( + provider, toHex(endpoint)).call() + + return Curve([int(t) for t in terms]) + + def encode_params(endpoint_params: list = [], ): + pars = endpoint_params + hex_params =\ + [el if el.find('0x') == 0 else toHex(el) for el in pars] + bytes_params =\ + [bytearray(toBytes(hexstr=hex_p)) for hex_p in hex_params] + params = [] + + from math import ceil + for element in bytes_params: + if len(element) <= 32: + params.append(toHex(element)) + continue + chunks_len = ceil((len(element) + 2) / 32) + param_bytes_w_len = [0, chunks_len].extend(element) + for i in range(0, chunks_len): + start = i * 32 + end = start + 32 + params.append(toHex(param_bytes_w_len[start:end])) + return params + + def decode_params(self, raw_params: list = []): + bytes_params = [bytearray(toBytes(hexstr=el)) for el in raw_params] + params = [] + i = 0 + length = len(bytes_params) + + while i < length: + is_start_o_chunks =\ + bytes_params[i][0] == 0 and\ + bytes_params[i][1] > 1 and\ + len(bytes_params[i]) == 32 + + if not is_start_o_chunks: + params.append(toHex(bytes_params[i])) + i += 1 + continue + + chunks_len = bytes_params[i][1] + end = i + chunks_len + + raw_bytes = bytes_params[i][2:] + i += 1 + + while i < end: + raw_bytes = raw_bytes.extend(bytes_params[i]) + i += 1 + + params.append(toHex(raw_bytes)) + + try: + return [toText(hexstr=raw_hex) for raw_hex in params] + except Exception as e: + print(e) + + async def set_endpoint_params(self, endpoint, From, gasPrice, + cb, endpoint_params=[], + gas=const.DEFAULT_GAS): + params = self.encode_params(endpoint_params) + + try: + tx_hash = await\ + self.contract.functions.setEndpointParams( + toHex(text=endpoint), params).transact( + {"from": From, "gas": gas}) + cb(None, tx_hash) + except Exception as e: + cb(e) + + return tx_hash + + async def get_endpoint_broker(self, provider: address, endpoint: str): + return\ + await self.contract.functions.getEndpointBroker(provider, + toHex(text=endpoint)).call() + + async def is_endpoint_set(self, provider: address, endpoint: str): + unset: bool = await\ + self.contract.functions.getCurveUnset(provider, + toHex(text=endpoint)).call() + return not unset + + """ Events + """ + from typing import Callable + + async def listen(self, callback: Callable[..., None]): + self.contract.events.allEvents(callback) + + async def listen_new_provider(self, callback: TransactionCallback, + filters: Filter = {}): + self.contract.events.NewProvider(filters, callback) + + async def listen_new_curve(self, callback: TransactionCallback, + filters: Filter): + self.contract.events.NewCurve(filters, callback) diff --git a/src/Types/types.py b/src/Types/types.py new file mode 100644 index 00000000..edb2e637 --- /dev/null +++ b/src/Types/types.py @@ -0,0 +1,220 @@ +from typing import TypedDict, Callable, Any, NewType +from collections import namedtuple + +address = NewType("address", str) +txid = NewType("txid", str) +NumType = NewType("NumType", float) # need confirmation on this datatype + +""" + Python has a builtin called the TypedDict that + is structured and can be accessed in a similar way + to a TypeScript interface + + Py also lacks constants, but named tuples behaves + the same way +""" + + +class defaultTx(TypedDict, total=False): + From: str + gas: float + gasPrice: float + + +class Filter(TypedDict, total=False): + fromBlock: float + toBlock: float + provider: str + subscriber: str + terminator: str + endpoint: str + ID: float + + +class listenEvent(TypedDict, total=False): + filtr: Filter + callback: Callable[..., Any] # accepts a function, takes any # of args + + +class Artifact(TypedDict, total=False): + contract_name: str + abi: dict + networks: dict = {"networkId": {"address": str}} + + +class BaseContractType(TypedDict, total=False): + """ Base Contract """ + artifactsDir: str + artifactName: str + networkId: int + networkProvider: Any or None + contract: Any + coordinator: str + address: str + web3: Any + + +class NetworkProviderOptions(TypedDict, total=False): + artifactsDir: str + networkId: int + networkProvider: Any + coordinator: str + address: str + web3: Any + + +class TransferType(defaultTx, TypedDict): + to: str + amount: float + + +Constants = namedtuple("Constants", ["DEFAULT_GAS", "NULL_ADDRESS"]) +const = Constants(4e5, "0x0000000000000000000000000000000000000000") +""" + accessed by const.DEFAULT_GAS and const.NULL_ADDRESS +""" + + +class SubscriptionInit(defaultTx, TypedDict, total=False): + provider: str + endpoint: str + endpoint_params: list + blocks: NumType + pubkey: NumType + + +class SubscriptionEnd(defaultTx, TypedDict, total=False): + provider: str + + +class SubscriptionType(TypedDict): + provider: str + subscriber: str + endpoint: str + + +############# BONDAGE ############# + +class TokenBondType(defaultTx, TypedDict): + endpoint: str + dots: NumType + + +class BondType(defaultTx, TypedDict, total=False): + subscriber: str + provider: str + endpoint: str + dots: NumType + + +class DelegateBondType(BondType, TypedDict): + subscriber: str + + +class UnbondType(defaultTx, TypedDict): + provider: str + endpoint: str + dots: NumType + + +class SubscribeType(defaultTx, TypedDict): + provider: str + endpoint: str + dots: NumType + endpoint_params: list # need confirmation on this data type + + +class SubscriberHandler(TypedDict, total=False): + """ + Callable types accepts a function, takes any # of args + """ + handleResponse: Callable[..., Any] + handleUnsubscription: Callable[..., Any] + handleSubscription: Callable[..., Any] + + +class ApproveType(defaultTx, TypedDict): + provider: str + zapNum: float + + +class BondArgs(defaultTx, TypedDict): + provider: str + endpoint: str + dots: NumType + + +class UnbondArgs(defaultTx, TypedDict): + provider: str + endpoint: str + dots: NumType + + +class DelegateBondArgs(defaultTx, TypedDict): + provider: str + endpoint: str + dots: NumType + subscriber: str + + +class BondageArgs(TypedDict, total=False): + subscriber: str + provider: str + endpoint: str + dots: NumType + zapNum: NumType + + +class CalcBondRateType(TypedDict): + provider: str + endpoint: str + zapNum: NumType + + +class BondFilter(Filter, TypedDict, total=False): + numDots: NumType + numZap: NumType + + +############# TOKEN DOT FACTORY ############# + +# accepts a function, takes error and hash as args +TransactionCallback = NewType( + "TransactionCallback", Callable[str, str], None) + + +############# PROVIDER ############# + +class InitProvider(defaultTx, TypedDict): + public_key: str + title: str + + +class InitCurve(defaultTx, TypedDict, total=False): + endpoint: str + term: list + broker: address + + +class NextEndpoint(TypedDict): + provider: address + endpoint: str + + +class EndpointParams(defaultTx, TypedDict): + endpoint: str + endpoint_params: list + + +class SetProviderParams(defaultTx, TypedDict): + key: str + value: str + + +class SetProviderTitle(defaultTx, TypedDict): + From: address + title: str + + +class Endpoint(defaultTx, TypedDict): + endpoint: str From 6313d05b1ea0ce356bff28af9a01ec15ebdf29b4 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Fri, 5 Mar 2021 00:09:21 -0400 Subject: [PATCH 03/20] Began writing tests for Registry; properly imported Web3 utils; Having issues mocking BaseContract; --- src/Registry/registry.py | 47 ++++++++++++----------- tests/Registry/test_registry.py | 68 +++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 tests/Registry/test_registry.py diff --git a/src/Registry/registry.py b/src/Registry/registry.py index e1216e8a..e14d93a0 100644 --- a/src/Registry/registry.py +++ b/src/Registry/registry.py @@ -1,4 +1,5 @@ -from web3.Web3 import toHex, toText, toBytes +from web3 import Web3 +# from Web3 import Web3.toHex, Web3.toText, Web3.toBytes from BaseContract.base_contract import BaseContract from ZapToken.Curve.curve import Curve @@ -25,8 +26,8 @@ class ZapRegistry(BaseContract): ZapRegistry({"networkId": 42, "networkProvider": "web3"}) """ - def __init__(self, options: NetworkProviderOptions): - options["artifactName"] = "REGISTRY" + def __init__(self, options: NetworkProviderOptions = {}): + options["artifact_name"] = "REGISTRY" super().__init__(**options) """ @@ -78,7 +79,7 @@ async def initiate_provider(self, public_key: str, title: str, try: tx_hash = await self.contract.functions.InitiateProvider( - str(public_key), toHex(title)).transact( + str(public_key), Web3.toHex(title)).transact( {"from": From, "gas": gas}) cb(None, tx_hash) @@ -103,7 +104,7 @@ async def get_providertitle(self, provider: address): title = await\ self.contract.functions.getProviderTitle(provider).call() - return toText(title) + return Web3.toText(title) async def set_provider_title(self, From: address, title: str, cb: TransactionCallback, gas=const.DEFAULT_GAS): @@ -117,7 +118,7 @@ async def set_provider_title(self, From: address, title: str, """ try: tx_hash = await\ - self.contract.functions.setProviderTitle(toHex(title)).transact( + self.contract.functions.setProviderTitle(Web3.toHex(title)).transact( {"from": From, "gas": gas}) cb(None, tx_hash) @@ -130,7 +131,7 @@ async def is_provider_initiated(self, provider: address): return await self.contract.is_provider_initiated(provider) async def get_provider_param(self, provider: address, key: str): - return await self.contract.functions.getProviderParameter(provider, toHex(key)) + return await self.contract.functions.getProviderParameter(provider, Web3.toHex(key)) async def get_all_provider_params(self, provider: address): return await self.contract.functions.getAllProviderParams(provider).call() @@ -138,7 +139,7 @@ async def get_all_provider_params(self, provider: address): async def get_provider_endpoints(self, provider: address): endpoints = await\ self.contract.functions.getProviderEndpoints(provider).call() - endpoints = [toText(endpoint) for endpoint in endpoints] + endpoints = [Web3.toText(endpoint) for endpoint in endpoints] valid_endpoints = [e for e in endpoints if e != ''] return valid_endpoints @@ -152,11 +153,11 @@ async def initiate_provider_curve(self, end_point, term, broker=const.NULL_ADDRESS, gas=const.DEFAULT_GAS): """""" - hex_terms = [toHex(t) for t in term] + hex_terms = [Web3.toHex(t) for t in term] try: tx_hash = await\ - self.contract.functions.initiateProviderCurve(toHex(end_point), + self.contract.functions.initiateProviderCurve(Web3.toHex(end_point), hex_terms, broker) cb(None, tx_hash) except Exception as e: @@ -168,7 +169,7 @@ async def clear_endpoint(self, endpoint, From, gasPrice, cb: TransactionCallback, gas=const.DEFAULT_GAS): try: tx_hash = await\ - self.contract.functions.clearEndpoint(toHex(endpoint)).send( + self.contract.functions.clearEndpoint(Web3.toHex(endpoint)).send( {"from": From, "gas": gas}) cb(None, tx_hash) except Exception as e: @@ -179,33 +180,33 @@ async def clear_endpoint(self, endpoint, From, gasPrice, async def get_provider_curve(self, provider: address, endpoint: str): terms: list = await\ self.contract.functions.getProviderCurve( - provider, toHex(endpoint)).call() + provider, Web3.toHex(endpoint)).call() return Curve([int(t) for t in terms]) def encode_params(endpoint_params: list = [], ): pars = endpoint_params hex_params =\ - [el if el.find('0x') == 0 else toHex(el) for el in pars] + [el if el.find('0x') == 0 else Web3.toHex(el) for el in pars] bytes_params =\ - [bytearray(toBytes(hexstr=hex_p)) for hex_p in hex_params] + [bytearray(Web3.toBytes(hexstr=hex_p)) for hex_p in hex_params] params = [] from math import ceil for element in bytes_params: if len(element) <= 32: - params.append(toHex(element)) + params.append(Web3.toHex(element)) continue chunks_len = ceil((len(element) + 2) / 32) param_bytes_w_len = [0, chunks_len].extend(element) for i in range(0, chunks_len): start = i * 32 end = start + 32 - params.append(toHex(param_bytes_w_len[start:end])) + params.append(Web3.toHex(param_bytes_w_len[start:end])) return params def decode_params(self, raw_params: list = []): - bytes_params = [bytearray(toBytes(hexstr=el)) for el in raw_params] + bytes_params = [bytearray(Web3.toBytes(hexstr=el)) for el in raw_params] params = [] i = 0 length = len(bytes_params) @@ -217,7 +218,7 @@ def decode_params(self, raw_params: list = []): len(bytes_params[i]) == 32 if not is_start_o_chunks: - params.append(toHex(bytes_params[i])) + params.append(Web3.toHex(bytes_params[i])) i += 1 continue @@ -231,10 +232,10 @@ def decode_params(self, raw_params: list = []): raw_bytes = raw_bytes.extend(bytes_params[i]) i += 1 - params.append(toHex(raw_bytes)) + params.append(Web3.toHex(raw_bytes)) try: - return [toText(hexstr=raw_hex) for raw_hex in params] + return [Web3.toText(hexstr=raw_hex) for raw_hex in params] except Exception as e: print(e) @@ -246,7 +247,7 @@ async def set_endpoint_params(self, endpoint, From, gasPrice, try: tx_hash = await\ self.contract.functions.setEndpointParams( - toHex(text=endpoint), params).transact( + Web3.toHex(text=endpoint), params).transact( {"from": From, "gas": gas}) cb(None, tx_hash) except Exception as e: @@ -257,12 +258,12 @@ async def set_endpoint_params(self, endpoint, From, gasPrice, async def get_endpoint_broker(self, provider: address, endpoint: str): return\ await self.contract.functions.getEndpointBroker(provider, - toHex(text=endpoint)).call() + Web3.toHex(text=endpoint)).call() async def is_endpoint_set(self, provider: address, endpoint: str): unset: bool = await\ self.contract.functions.getCurveUnset(provider, - toHex(text=endpoint)).call() + Web3.toHex(text=endpoint)).call() return not unset """ Events diff --git a/tests/Registry/test_registry.py b/tests/Registry/test_registry.py new file mode 100644 index 00000000..20ff1e58 --- /dev/null +++ b/tests/Registry/test_registry.py @@ -0,0 +1,68 @@ +from pytest import ( + MonkeyPatch, raises, fixture +) + +from unittest.mock import MagicMock, patch + + +from os.path import join, realpath +import sys +sys.path.insert(0, realpath(join(__file__, "../../../src/"))) + +from Artifacts.src.index import Artifacts + + +MockContract = MagicMock( + abi=['abi'], address="0x000000000000000000", name="MOCKCONTRACT") + + +@patch('Registry.registry.Web3', autospec=True) +@patch('Registry.registry.NetworkProviderOptions', autospec=True) +@patch('ZapToken.Curve.curve.Curve', autospec=True) +def _ZapRegistry(mock_Curve, mock_npo, mock_Web3): + + mock_Web3.return_value = MagicMock() + w3 = mock_Web3() + + mock_npo.return_value = {} + + mp = MonkeyPatch() + # same as setting the return_value of a mock obj + mp.setattr(w3.eth, "contract", MockContract) + + class MockBaseContract: + def __init__(self, artifact_name, artifact_dir=None, network_id=None, + network_provider=None, coordinator=None, + address=None, web3=None): + self.name = artifact_name + self.artifact = Artifacts[artifact_name] + self.provider = web3 or w3 + self.networkId = "1" + self.coordinator = MockContract + self.address = self.artifact["networks"][self.networkId]["address"] + MockContract1 = MagicMock(abi=self.artifact["abi"], + address=self.address) + self.contract = MockContract1 + + # sets the returned class as MockBaseContract + mp.setattr("Registry.registry.BaseContract", MockBaseContract) + from Registry.registry import BaseContract + print(BaseContract("REGISTRY").__dict__) # ✔️ all good + from Registry.registry import ZapRegistry + + return(ZapRegistry()) # ❌ uses the original BaseContract + + +# @fixture() +# def ZapRegistry(): +# return _ZapRegistry() + + +def test_init(ZapRegistry): + assert ZapRegistry + + +print(_ZapRegistry().__dict__) + +# def test_init(): +# assert isinstance(_ZapRegistry(), ZapRegistry) From 1708a1b5c4097cb2ad9f7aae3ae7e3a8ff997c26 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Fri, 5 Mar 2021 00:09:59 -0400 Subject: [PATCH 04/20] fixed Tx Callback `Callable` type. --- src/Types/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Types/types.py b/src/Types/types.py index edb2e637..38373d1d 100644 --- a/src/Types/types.py +++ b/src/Types/types.py @@ -180,7 +180,7 @@ class BondFilter(Filter, TypedDict, total=False): # accepts a function, takes error and hash as args TransactionCallback = NewType( - "TransactionCallback", Callable[str, str], None) + "TransactionCallback", Callable[[str, str], None]) ############# PROVIDER ############# From 162d88fbc3d4dd45f6b95907933d35dc5f16e7bc Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Fri, 5 Mar 2021 08:11:01 -0400 Subject: [PATCH 05/20] Fixed problem with Registry not using patched BaseContract; Also added docs strings. --- src/Registry/registry.py | 2 +- tests/Registry/test_registry.py | 87 ++++++++++++++++++++++++++------- 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/Registry/registry.py b/src/Registry/registry.py index e14d93a0..af115ceb 100644 --- a/src/Registry/registry.py +++ b/src/Registry/registry.py @@ -28,7 +28,7 @@ class ZapRegistry(BaseContract): def __init__(self, options: NetworkProviderOptions = {}): options["artifact_name"] = "REGISTRY" - super().__init__(**options) + BaseContract.__init__(self, **options) """ Registry storage calls for all providers diff --git a/tests/Registry/test_registry.py b/tests/Registry/test_registry.py index 20ff1e58..fe598ee1 100644 --- a/tests/Registry/test_registry.py +++ b/tests/Registry/test_registry.py @@ -2,7 +2,7 @@ MonkeyPatch, raises, fixture ) -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch, AsyncMock from os.path import join, realpath @@ -15,54 +15,105 @@ MockContract = MagicMock( abi=['abi'], address="0x000000000000000000", name="MOCKCONTRACT") +""" Arrange/SetUp Section +""" + @patch('Registry.registry.Web3', autospec=True) -@patch('Registry.registry.NetworkProviderOptions', autospec=True) -@patch('ZapToken.Curve.curve.Curve', autospec=True) -def _ZapRegistry(mock_Curve, mock_npo, mock_Web3): +def _ZapRegistry(mock_Web3): + """ WIP: Returns an object representation of ZapRegistry for testing. + + This ZapRegistry object has a mocked BaseContract + used for its init phase. + + This mocked BaseContract has fixture data, including its contract. + + Contracts (and/or their functions) are asynchronous mock objects. + Almost everything else is base of Magic mock objects. + + """ mock_Web3.return_value = MagicMock() w3 = mock_Web3() - mock_npo.return_value = {} - mp = MonkeyPatch() # same as setting the return_value of a mock obj mp.setattr(w3.eth, "contract", MockContract) - class MockBaseContract: + class MockBaseContract(): def __init__(self, artifact_name, artifact_dir=None, network_id=None, network_provider=None, coordinator=None, address=None, web3=None): + self.name = artifact_name self.artifact = Artifacts[artifact_name] self.provider = web3 or w3 self.networkId = "1" self.coordinator = MockContract self.address = self.artifact["networks"][self.networkId]["address"] - MockContract1 = MagicMock(abi=self.artifact["abi"], + MockContract1 = AsyncMock(abi=self.artifact["abi"], address=self.address) self.contract = MockContract1 + # return value evals to none right now, need to know result of call + self.contract.functions.getAllOracles.call.return_value = None - # sets the returned class as MockBaseContract mp.setattr("Registry.registry.BaseContract", MockBaseContract) - from Registry.registry import BaseContract - print(BaseContract("REGISTRY").__dict__) # ✔️ all good from Registry.registry import ZapRegistry - return(ZapRegistry()) # ❌ uses the original BaseContract + try: + return(ZapRegistry()) + except Exception as e: + raise e + + +@fixture() +def Zap_Registry(): + """ yield a ZapRegistry object + + This object stays in pytest cache for the lifespan of the test. + It is not necessary to have a SetUp class method/function. + + references: + https://docs.pytest.org/en/stable/xunit_setup.html + https://docs.pytest.org/en/stable/fixture.html#what-fixtures-are + https://docs.pytest.org/en/stable/fixture.html#\ + fixtures-can-be-requested-more-than-once-per-test-return-values-are-cached + """ + zap_reg_obj = _ZapRegistry + yield zap_reg_obj + + +""" pytest section +""" + + +def test_init(Zap_Registry): + """ WIP: Test if ZapRegistry is initialized + + Also tests if it has an attribute `name` that evaluates to "REGISTRY" + """ + instance = Zap_Registry() + assert instance + assert instance.name == "REGISTRY" -# @fixture() -# def ZapRegistry(): -# return _ZapRegistry() +async def task(co_mock): + """ Will handle awaitables from contract calls/tx + """ + await co_mock +""" python "direct calls" section +""" -def test_init(ZapRegistry): - assert ZapRegistry +# import pprint +# from asyncio import run +# provs = asyncio.run(_ZapRegistry().get_all_providers()) +# zap_reg_obj = _ZapRegistry() +# provs = zap_reg_obj.get_all_providers() +# print(run(task(zap_reg_obj.contract.functions.getAllOracles()))) -print(_ZapRegistry().__dict__) +# pprint(zap_reg_obj.contract.functions.getAllOracles.__dict__) # def test_init(): # assert isinstance(_ZapRegistry(), ZapRegistry) From be0408acf0419e61a0f151b385b8a6e457fef347 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Thu, 11 Mar 2021 19:07:02 -0400 Subject: [PATCH 06/20] Added more tests, fixed issues with existing ones --- tests/Registry/test_registry.py | 318 +++++++++++++++++++++++++++++--- 1 file changed, 290 insertions(+), 28 deletions(-) diff --git a/tests/Registry/test_registry.py b/tests/Registry/test_registry.py index fe598ee1..a34e22bb 100644 --- a/tests/Registry/test_registry.py +++ b/tests/Registry/test_registry.py @@ -1,26 +1,94 @@ +""" Tests the setting up, transactions and calls of the Zap Registry contract. + + This is an integration test that uses the Zap Hardhat testing environment + to test the output and side effects of Zap Registry. + + Web3 is initialized and used to make calls and transactions to Hardhat and + receive events from the testing environment. + + Uses Pytest's fixture decorators to facilitate the setup of this test. + Fixtures are ran once the first time they're requested and their return + values are cached for the remainder of the test. +""" from pytest import ( - MonkeyPatch, raises, fixture + MonkeyPatch, raises, fixture, mark ) -from unittest.mock import MagicMock, patch, AsyncMock - +from web3 import Web3 from os.path import join, realpath import sys +from subprocess import run sys.path.insert(0, realpath(join(__file__, "../../../src/"))) + from Artifacts.src.index import Artifacts +from Types.types import const, txid +from ZapToken.Curve.curve import Curve + + +# @fixture +# def test_provider(): +# return EthereumTesterProvider() + + +# @fixture +# def tester(test_provider): +# return test_provider.ethereum_tester + + +# @fixture +# def w3(test_provider): +# return Web3(test_provider) + +# @fixture(autouse=True) +# def prepare_hh(): +# run(["wsl", "-d Ubuntu", "-e", "killall node"], shell=True) +# run(["wsl", "-d Ubuntu", "-e", +# "~/projects/zap-hardhat/start.sh"], shell=True) + + +w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) +abi = Artifacts["REGISTRY"] +coor_artifact = Artifacts["ZAPCOORDINATOR"] +_address = abi["networks"]["31337"]["address"] + + +@fixture +def reg_contract(): + _reg_contract = w3.eth.contract(address=_address, + abi=abi["abi"]) + return _reg_contract + + +@fixture +def coor(): + _coor = w3.eth.contract(abi=coor_artifact['abi'], + address=coor_artifact['networks']["31337"]['address']) + return _coor + +@fixture +def funcs(reg_contract): + _funcs = reg_contract.functions.__dict__ + del _funcs['_functions'] + del _funcs['abi'] + return _funcs + + +@fixture +def coor_funcs(coor): + _coor_funcs = coor.functions.__dict__ + del _coor_funcs['_functions'] + del _coor_funcs['abi'] + return _coor_funcs -MockContract = MagicMock( - abi=['abi'], address="0x000000000000000000", name="MOCKCONTRACT") """ Arrange/SetUp Section """ -@patch('Registry.registry.Web3', autospec=True) -def _ZapRegistry(mock_Web3): +def _ZapRegistry(reg_contract): """ WIP: Returns an object representation of ZapRegistry for testing. This ZapRegistry object has a mocked BaseContract @@ -33,35 +101,29 @@ def _ZapRegistry(mock_Web3): """ - mock_Web3.return_value = MagicMock() - w3 = mock_Web3() - mp = MonkeyPatch() # same as setting the return_value of a mock obj - mp.setattr(w3.eth, "contract", MockContract) - class MockBaseContract(): + class MockBaseContract: def __init__(self, artifact_name, artifact_dir=None, network_id=None, network_provider=None, coordinator=None, address=None, web3=None): self.name = artifact_name self.artifact = Artifacts[artifact_name] + coor_artifact = Artifacts["ZAPCOORDINATOR"] self.provider = web3 or w3 - self.networkId = "1" - self.coordinator = MockContract - self.address = self.artifact["networks"][self.networkId]["address"] - MockContract1 = AsyncMock(abi=self.artifact["abi"], - address=self.address) - self.contract = MockContract1 - # return value evals to none right now, need to know result of call - self.contract.functions.getAllOracles.call.return_value = None + self.network_id = network_id or "1" + self.coordinator = self.provider.eth.contract(abi=coor_artifact['abi'], + address=coor_artifact['networks'][self.network_id]['address']) + self.address = self.artifact["networks"][self.network_id]["address"] + self.contract = reg_contract mp.setattr("Registry.registry.BaseContract", MockBaseContract) from Registry.registry import ZapRegistry try: - return(ZapRegistry()) + return(ZapRegistry({"network_id": "31337"})) except Exception as e: raise e @@ -81,20 +143,220 @@ def Zap_Registry(): """ zap_reg_obj = _ZapRegistry yield zap_reg_obj + del zap_reg_obj + + +@fixture +def functions(reg_contract): + return reg_contract.functions + + +# @fixture +# def all_oracles(functions): +# return functions.getAllOracles().call() + + +@fixture +def oracle(functions): + return functions.getOracleAddress(0).call() + + +@fixture +def account(): + return w3.eth.accounts[11] + + +@fixture +def pubkey(functions, account): + return functions.getPublicKey(account).call() + + +@fixture +def anyio_backend(): + """ Ensures anyio uses the default, pytest-asyncio plugin + for running async tests + """ + return 'asyncio' """ pytest section """ -def test_init(Zap_Registry): - """ WIP: Test if ZapRegistry is initialized - - Also tests if it has an attribute `name` that evaluates to "REGISTRY" +class TestRegistry: + """ Tests the initialization and functionality of Zap's Registry Contract """ - instance = Zap_Registry() - assert instance - assert instance.name == "REGISTRY" + + @fixture + def instance(self, Zap_Registry, reg_contract): + """ SetUp: ZapRegistry instance. + + This instance is cached and can be reused for + the remainder of the test. + """ + instance = Zap_Registry(reg_contract) + return instance + + def test_init(self, instance, funcs, coor_funcs): + """ Test if ZapRegistry is initialized + + Tests is the static attributes are all present and are valid + """ + contract = instance.contract + coor = instance.coordinator + functions_dict = contract.functions.__dict__ + coordinator_dict = coor.functions.__dict__ + + assert instance + assert instance.name == "REGISTRY" + assert instance.artifact == Artifacts["REGISTRY"] + assert isinstance(instance.artifact, dict) + assert isinstance(instance.provider, Web3) + assert instance.provider == instance.contract.web3 + assert instance.network_id == "31337" + assert instance.address == "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + assert instance.address == instance.contract.address + + for func, coor_func in zip(funcs, coor_funcs): + assert func in functions_dict + assert coor_func in coordinator_dict + + # @mark.skip(reason="Provider already initialized.") + @mark.anyio + async def test_initiate_provider(self, instance, pubkey, account): + opt = {"public_key": pubkey, "title": "Registry", + "From": account, "gas": 4 * 10**5} + tx = await instance.initiate_provider(**opt) + + assert isinstance(tx, str) + + @mark.anyio + async def test_get_provider_publickey(self, instance, pubkey, account): + pubkey_instance = await instance.get_provider_publickey(account) + + assert pubkey_instance + assert isinstance(pubkey_instance, int) + assert pubkey_instance == pubkey + + @mark.anyio + async def test_get_provider_title(self, instance, account): + title_instance = await instance.get_provider_title(account) + + assert title_instance + assert isinstance(title_instance, str) + # assert title_instance == + + @mark.anyio + async def test_is_provider_initiated(self, instance, account): + isInit = await instance.is_provider_initiated(account) + + assert isInit is True + assert isinstance(isInit, bool) + + @mark.anyio + async def test_set_provider_title(self, instance, account): + title_test = "REGISTRY" + + tx = await instance.set_provider_title(account, title_test) + + assert tx + print(tx) + assert isinstance(tx, str) + + @mark.anyio + async def test_set_provider_param(self, instance, pubkey, account): + tx = await instance.set_provider_param(pubkey, 11, account) + + assert tx + assert isinstance(tx, str) + + @mark.anyio + async def test_get_provider_param(self, instance, account, pubkey): + param = await instance.get_provider_param( + account, Web3.toBytes(pubkey)) + + assert param + print(param) + assert isinstance(param, bytes) + + @mark.anyio + async def test_get_all_providers(self, instance): + provs = await instance.get_all_providers() + assert provs + assert isinstance(provs, list) + + @mark.anyio + async def get_all_provider_params(self, instance, address, pubkey): + params = await instance.get_provider_param( + address, pubkey) + + assert params + print(params) + assert isinstance(params, list) + + # @mark.skip(reason="Possible error with python types") + @mark.anyio + async def test_initiate_provider_curve(self, instance, account): + term = [3, 0, 0, 3, 100000] + opts = {"end_point": 'hi', "term": term, + "From": account, "gasPrice": int(5e4)} + + curve = await instance.initiate_provider_curve(**opts) + + assert curve + assert isinstance(curve, str) + + @mark.anyio + async def test_get_provider_address_by_index(self, instance): + addy = await instance.get_provider_address_by_index(0) + assert addy + assert isinstance(addy, str) + + @mark.anyio + async def test_get_provider_endpoints(self, instance, account): + e = await instance.get_provider_endpoints(account) + + assert e + print(e) + assert isinstance(e, list) + + return e + + @fixture + async def endpoints(self, instance, account): + e = await instance.get_provider_endpoints(account) + + return e + + @mark.anyio + async def test_get_provider_curve(self, instance, + account, endpoints): + e = endpoints + curve = await instance.get_provider_curve(account, e[-1]) + + assert curve + print(curve.values) + assert isinstance(curve, Curve) + + @mark.anyio + async def test_clear_endpoint(self, instance, + endpoints, + account): + e = endpoints + tx = await instance.clear_endpoint( + e[-1], account, int(5e3)) + + assert tx + print(tx) + assert isinstance(tx, str) + + # @mark.anyio + # async def test_(self, instance): + # get = await instance.something() + + # assert get + # print(get) + # assert isinstance(get, bytes) async def task(co_mock): From 5aa5fc8519dd586d0713a501e8fb8c9e9e781389 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Thu, 11 Mar 2021 19:11:19 -0400 Subject: [PATCH 07/20] Fixed Registry based on tests; Fixed async, has a static timer for each call `asyncio.sleep(delay)`; might need to change this in the future; Fixed ToHex/ToBytes confusion based from zap-monorepo; Fixed callback handling --- src/Registry/registry.py | 194 +++++++++++++++++++++++++-------------- 1 file changed, 124 insertions(+), 70 deletions(-) diff --git a/src/Registry/registry.py b/src/Registry/registry.py index af115ceb..a5783a21 100644 --- a/src/Registry/registry.py +++ b/src/Registry/registry.py @@ -1,11 +1,12 @@ from web3 import Web3 -# from Web3 import Web3.toHex, Web3.toText, Web3.toBytes +from asyncio import sleep +from typing import Optional, List, Dict from BaseContract.base_contract import BaseContract from ZapToken.Curve.curve import Curve from Types.types import ( Filter, address, - NetworkProviderOptions, const, TransactionCallback + NetworkProviderOptions, const, TransactionCallback, txid ) @@ -36,33 +37,35 @@ def __init__(self, options: NetworkProviderOptions = {}): # async def task(func, action, args1=[], args2=[]): # item = func(*args1).action(*args2) - # asyncio.sleep(5) + # sleep(5) # if item: # return item # return - async def get_all_providers(self): + async def get_all_providers(self) -> list: """Get all providers in Registry Contract. returns a list of oracles once async is fulfilled """ - await self.contract.functions.getAllOracles().call() + await sleep(3) + return self.contract.functions.getAllOracles().call() - async def get_provider_address_by_index(self, index: int): + async def get_provider_address_by_index(self, index: int) -> str: """ Look up provider's address by its index in registry storage returns address of indexed provider once async is fulfilled """ - await self.contract.functions.getOracleAddress(index).call() + await sleep(0.6) + return self.contract.functions.getOracleAddress(index).call() """ Provider specific calls """ async def initiate_provider(self, public_key: str, title: str, - From: address, cb: TransactionCallback, - gas=const.DEFAULT_GAS): + From: address, cb: TransactionCallback = None, + gas=const.DEFAULT_GAS) -> txid: """ Initiates a brand endpoint in the Registry contract, creating an Oracle entry if need be. @@ -78,68 +81,111 @@ async def initiate_provider(self, public_key: str, title: str, """ try: - tx_hash = await self.contract.functions.InitiateProvider( - str(public_key), Web3.toHex(title)).transact( + await sleep(3) + tx_hash: txid = self.contract.functions.initiateProvider( + public_key, Web3.toBytes(text=title)).transact( {"from": From, "gas": gas}) + if cb: + cb(None, tx_hash) - cb(None, tx_hash) - except Exception as e: - cb(e) - - return tx_hash + return tx_hash.hex() + except ValueError as e: + return str(e) - async def get_provider_publickey(self, provider: address): + async def get_provider_publickey(self, provider: address) -> int: """ Get a provider's public key from the registry contract. provider -- The address of this provider returns the public key number. """ - pub_key: str = await\ - self.contract.functions.getProviderPublicKey(provider).call() + await sleep(0.8) + return self.contract.functions.getProviderPublicKey(provider).call() - return pub_key + async def get_provider_title(self, provider: address) -> str: + """ Get a provider's title from the Registry contract. - async def get_providertitle(self, provider: address): - title = await\ - self.contract.functions.getProviderTitle(provider).call() + address -- The address of this provider. - return Web3.toText(title) + return a future that will eventually resolve into a title string + """ + await sleep(1) + title = Web3.toText(self.contract.functions.getProviderTitle( + provider).call()) + + return title async def set_provider_title(self, From: address, title: str, - cb: TransactionCallback, gas=const.DEFAULT_GAS): + cb: TransactionCallback = None, + gas=const.DEFAULT_GAS) -> txid: """ Set the new provider's title - + Arguments: From -- The address of this provider title -- The new title of this provider - cb -- Callback for transactionHash event + cb -- Callback for transactionHash event """ try: - tx_hash = await\ - self.contract.functions.setProviderTitle(Web3.toHex(title)).transact( + await sleep(1.4) + tx_hash: txid = self.contract.functions.setProviderTitle( + Web3.toBytes(text=title)).transact( {"from": From, "gas": gas}) cb(None, tx_hash) + return tx_hash.hex() except Exception as e: - cb(e, None) + return(str(e)) + + async def is_provider_initiated(self, provider: address) -> bool: + """ Gets whether this provider has already been created. + + provider -- Gets whether this provider has already been created. + + returns a future that will eventually resolve a true/false value. + """ + await sleep(0.13) + return self.contract.functions.isProviderInitiated(provider).call() + + async def set_provider_param(self, key: int, value: int, + From: address, gas=const.DEFAULT_GAS) -> txid: + await sleep(1.2) + return self.contract.functions.setProviderParameter( + Web3.toBytes(key), Web3.toBytes(value)).transact( + {"from": From, "gas": gas}).hex() + + async def get_provider_param(self, provider: address, key: int) -> bytes: + """ Get a parameter from a provider - return tx_hash + provider -- The address of the provider + key -- The key you're getting - async def is_provider_initiated(self, provider: address): - return await self.contract.is_provider_initiated(provider) + returns a future that will be resolved with the value of the keys + """ + await sleep(0.82) + return self.contract.functions.getProviderParameter(provider, Web3.toBytes(key)).call() + + async def get_all_provider_params(self, provider: address) -> List[bytes]: + """ Get all the parameters of a provider - async def get_provider_param(self, provider: address, key: str): - return await self.contract.functions.getProviderParameter(provider, Web3.toHex(key)) + provider -- The address of the provider - async def get_all_provider_params(self, provider: address): + returns a future that will be resolved with all the keys + """ + await sleep(0.89) return await self.contract.functions.getAllProviderParams(provider).call() - async def get_provider_endpoints(self, provider: address): - endpoints = await\ - self.contract.functions.getProviderEndpoints(provider).call() - endpoints = [Web3.toText(endpoint) for endpoint in endpoints] + async def get_provider_endpoints(self, provider: address) -> List[str]: + """ Get the endpoints of a given provider + + provider -- The address of this provider + + returns a Future that will be eventually resolved with + the list of endpoints of the provider. + """ + await sleep(0.58) + endpoints = self.contract.functions.getProviderEndpoints(provider).call() + endpoints = [Web3.toHex(endpoint) for endpoint in endpoints] valid_endpoints = [e for e in endpoints if e != ''] return valid_endpoints @@ -149,38 +195,44 @@ async def get_provider_endpoints(self, provider: address): """ async def initiate_provider_curve(self, end_point, term, - From, gasPrice, cb, + From, gasPrice, + cb: TransactionCallback = None, broker=const.NULL_ADDRESS, - gas=const.DEFAULT_GAS): + gas=const.DEFAULT_GAS) -> txid: """""" - hex_terms = [Web3.toHex(t) for t in term] + await sleep(0.247) try: - tx_hash = await\ - self.contract.functions.initiateProviderCurve(Web3.toHex(end_point), - hex_terms, broker) - cb(None, tx_hash) - except Exception as e: - cb(e) + tx_hash: txid = self.contract.functions.initiateProviderCurve( + Web3.toBytes(text=end_point), + term, broker).transact( + {"from": From, "gas": gas, "gasPrice": int(gasPrice)}) + if cb: + cb(None, tx_hash) + except ValueError as e: + print(str(e)) - return tx_hash + return tx_hash.hex() async def clear_endpoint(self, endpoint, From, gasPrice, - cb: TransactionCallback, gas=const.DEFAULT_GAS): + cb: TransactionCallback = None, + gas=const.DEFAULT_GAS) -> txid: try: - tx_hash = await\ - self.contract.functions.clearEndpoint(Web3.toHex(endpoint)).send( - {"from": From, "gas": gas}) - cb(None, tx_hash) + await sleep(1.6) + tx_hash: txid = self.contract.functions.clearEndpoint( + Web3.toBytes(hexstr=endpoint)).transact( + {"from": From, "gas": gas, "gasPrice": gasPrice}) + if cb: + cb(None, tx_hash) except Exception as e: - cb(e) + print(e) - return tx_hash + return tx_hash.hex() async def get_provider_curve(self, provider: address, endpoint: str): - terms: list = await\ - self.contract.functions.getProviderCurve( - provider, Web3.toHex(endpoint)).call() + await sleep(0.8) + terms: list = self.contract.functions.getProviderCurve( + provider, Web3.toBytes(hexstr=endpoint)).call() return Curve([int(t) for t in terms]) @@ -245,20 +297,22 @@ async def set_endpoint_params(self, endpoint, From, gasPrice, params = self.encode_params(endpoint_params) try: - tx_hash = await\ - self.contract.functions.setEndpointParams( - Web3.toHex(text=endpoint), params).transact( - {"from": From, "gas": gas}) - cb(None, tx_hash) + await sleep() + tx_hash = self.contract.functions.setEndpointParams( + Web3.toHex(text=endpoint), params).transact( + {"from": From, "gas": gas}) + if cb: + cb(None, tx_hash) except Exception as e: - cb(e) + print(e) - return tx_hash + return tx_hash.hex() async def get_endpoint_broker(self, provider: address, endpoint: str): - return\ - await self.contract.functions.getEndpointBroker(provider, - Web3.toHex(text=endpoint)).call() + await sleep() + return self.contract.functions.getEndpointBroker(provider, + Web3.toHex( + text=endpoint)).call() async def is_endpoint_set(self, provider: address, endpoint: str): unset: bool = await\ From 4a4dcb30d217b5d5e84e6f90bd3ce6e7a397fbf6 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Thu, 11 Mar 2021 19:17:35 -0400 Subject: [PATCH 08/20] Fixed ZapTypes, Added on to Test ReadMe; Fixed DEFAULT_GAS, should be an integer, web3 doesn't recognise float whole numbers; Added info on how to run asyncio tests. --- src/Types/types.py | 2 +- tests/README.md | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Types/types.py b/src/Types/types.py index 38373d1d..794d5a43 100644 --- a/src/Types/types.py +++ b/src/Types/types.py @@ -69,7 +69,7 @@ class TransferType(defaultTx, TypedDict): Constants = namedtuple("Constants", ["DEFAULT_GAS", "NULL_ADDRESS"]) -const = Constants(4e5, "0x0000000000000000000000000000000000000000") +const = Constants(int(4e5), "0x0000000000000000000000000000000000000000") """ accessed by const.DEFAULT_GAS and const.NULL_ADDRESS """ diff --git a/tests/README.md b/tests/README.md index 3b64e74f..c5cfa7cc 100644 --- a/tests/README.md +++ b/tests/README.md @@ -8,6 +8,8 @@ cd path/to/zappy_root/ source ./venv/bin/activate # install pytest with pip pip3 install pytest +# install anyio to run async tests and web3 test pack +pip3 install anyio web3[tester] ``` ## Usage ```bash @@ -16,3 +18,11 @@ pytest -v The `-v` option will give you a detailed output of tests. For extended usage documentation, take a look at the [pytest docs](https://docs.pytest.org/en/stable/usage.html). + +## Asynchronous I/O Tests + +Async I/O testing is done with the [Anyio library](https://anyio.readthedocs.io/en/latest/index.html). + +Anyio provides a flexible and scalable library that adds support for pytest async tests. + +The Python builtin `asyncio` library is supported on install. [Anyio also supports curio and trio as backends](https://anyio.readthedocs.io/en/latest/testing.html?highlight=pytest#specifying-the-backends-to-run-on). From e6315f25247f07de662b6733028d05f13b38e40f Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Sun, 14 Mar 2021 12:55:09 -0400 Subject: [PATCH 09/20] Fixed and restructured Registry; fixed logic in tx calls; fixed errors in web3 hex/bytes conversions; --- src/Registry/registry.py | 84 +++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/src/Registry/registry.py b/src/Registry/registry.py index a5783a21..2c6986a0 100644 --- a/src/Registry/registry.py +++ b/src/Registry/registry.py @@ -90,7 +90,7 @@ async def initiate_provider(self, public_key: str, title: str, return tx_hash.hex() except ValueError as e: - return str(e) + print(e) async def get_provider_publickey(self, provider: address) -> int: """ Get a provider's public key from the registry contract. @@ -184,7 +184,8 @@ async def get_provider_endpoints(self, provider: address) -> List[str]: the list of endpoints of the provider. """ await sleep(0.58) - endpoints = self.contract.functions.getProviderEndpoints(provider).call() + endpoints = self.contract.functions.getProviderEndpoints( + provider).call() endpoints = [Web3.toHex(endpoint) for endpoint in endpoints] valid_endpoints = [e for e in endpoints if e != ''] @@ -209,37 +210,35 @@ async def initiate_provider_curve(self, end_point, term, {"from": From, "gas": gas, "gasPrice": int(gasPrice)}) if cb: cb(None, tx_hash) + return tx_hash.hex() except ValueError as e: print(str(e)) - return tx_hash.hex() - async def clear_endpoint(self, endpoint, From, gasPrice, cb: TransactionCallback = None, gas=const.DEFAULT_GAS) -> txid: try: await sleep(1.6) tx_hash: txid = self.contract.functions.clearEndpoint( - Web3.toBytes(hexstr=endpoint)).transact( - {"from": From, "gas": gas, "gasPrice": gasPrice}) + Web3.toBytes(text=endpoint)).transact( + {"from": From, "gas": gas, "gasPrice": gasPrice}) if cb: cb(None, tx_hash) + return tx_hash.hex() except Exception as e: print(e) - return tx_hash.hex() - async def get_provider_curve(self, provider: address, endpoint: str): await sleep(0.8) terms: list = self.contract.functions.getProviderCurve( - provider, Web3.toBytes(hexstr=endpoint)).call() + provider, Web3.toBytes(text=endpoint)).call() return Curve([int(t) for t in terms]) - def encode_params(endpoint_params: list = [], ): + def encode_params(self, endpoint_params: list = [], ): pars = endpoint_params hex_params =\ - [el if el.find('0x') == 0 else Web3.toHex(el) for el in pars] + [el if el.find('0x') == 0 else Web3.toHex(text=el) for el in pars] bytes_params =\ [bytearray(Web3.toBytes(hexstr=hex_p)) for hex_p in hex_params] params = [] @@ -257,8 +256,10 @@ def encode_params(endpoint_params: list = [], ): params.append(Web3.toHex(param_bytes_w_len[start:end])) return params - def decode_params(self, raw_params: list = []): - bytes_params = [bytearray(Web3.toBytes(hexstr=el)) for el in raw_params] + def decode_params(self, raw_params: List[str] = []): + bytes_params = [bytearray(Web3.toBytes(hexstr=el)) + + for el in raw_params] params = [] i = 0 length = len(bytes_params) @@ -286,38 +287,57 @@ def decode_params(self, raw_params: list = []): params.append(Web3.toHex(raw_bytes)) - try: - return [Web3.toText(hexstr=raw_hex) for raw_hex in params] - except Exception as e: - print(e) + try: + return [Web3.toText(hexstr=raw_hex) for raw_hex in params] + except Exception as e: + print(e) - async def set_endpoint_params(self, endpoint, From, gasPrice, - cb, endpoint_params=[], - gas=const.DEFAULT_GAS): + async def set_endpoint_params(self, endpoint: str, From: address, + gasPrice: int, + cb: Optional[TransactionCallback] = None, + endpoint_params: Optional[List[str]] = [], + gas: Optional[int] = const.DEFAULT_GAS + ) -> txid: + """ Initialize endpoint params for an endpoint. + Can only be called by the owner of this oracle. + + :param str endpoint: Data endpoint of the provider + :param List[str] endpoint_params: The parameters that this endpoint + accepts as query arguments + :param address from: The address of the owner of this oracle + :param int gas: Sets the gas limit for this + transaction (optional) + :param cb: Callback for transactionHash event + + :returns Future(txid) Returns a Promise that will eventually + resolve into a transaction hash + """ params = self.encode_params(endpoint_params) try: - await sleep() + await sleep(0.53) tx_hash = self.contract.functions.setEndpointParams( - Web3.toHex(text=endpoint), params).transact( + Web3.toBytes(text=endpoint), params).transact( {"from": From, "gas": gas}) if cb: cb(None, tx_hash) + return tx_hash.hex() except Exception as e: print(e) - return tx_hash.hex() - - async def get_endpoint_broker(self, provider: address, endpoint: str): - await sleep() - return self.contract.functions.getEndpointBroker(provider, - Web3.toHex( - text=endpoint)).call() + async def get_endpoint_broker(self, provider: address, + endpoint: str) -> str: + await sleep(0.27) + return self.contract.functions.getEndpointBroker( + provider, + Web3.toBytes(text=endpoint)).call() async def is_endpoint_set(self, provider: address, endpoint: str): - unset: bool = await\ - self.contract.functions.getCurveUnset(provider, - Web3.toHex(text=endpoint)).call() + await sleep(0.2) + unset: bool = self.contract.functions.getCurveUnset( + provider, + Web3.toBytes(text=endpoint)).call() + return not unset """ Events From bfddb41b2776582ecaf889e1d5a165dd0b790cdb Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Sun, 14 Mar 2021 12:56:50 -0400 Subject: [PATCH 10/20] Added and fixed tests, all passing; Added conftest.py in Registry directory, sets up fixtures for registry test. --- tests/Registry/conftest.py | 190 +++++++++++++++++++++++++ tests/Registry/test_registry.py | 245 +++++++------------------------- 2 files changed, 242 insertions(+), 193 deletions(-) create mode 100644 tests/Registry/conftest.py diff --git a/tests/Registry/conftest.py b/tests/Registry/conftest.py new file mode 100644 index 00000000..42d8db6b --- /dev/null +++ b/tests/Registry/conftest.py @@ -0,0 +1,190 @@ +from pytest import MonkeyPatch, fixture + +from web3 import Web3 +# from Types.types import const, txid + +from os.path import join, realpath +from sys import path +path.insert(0, realpath(join(__file__, "../../../src/"))) + +from Artifacts.src.index import Artifacts +from ZapToken.Curve.curve import Curve + +w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) +abi = Artifacts["REGISTRY"] +coor_artifact = Artifacts["ZAPCOORDINATOR"] +_address = abi["networks"]["31337"]["address"] + + +""" Arrange/SetUp Section +""" + + +@fixture(scope="module") +def reg_contract(): + _reg_contract = w3.eth.contract(address=_address, + abi=abi["abi"]) + return _reg_contract + + +@fixture(scope="module") +def coor(): + _coor = w3.eth.contract( + abi=coor_artifact['abi'], + address=coor_artifact['networks']["31337"]['address']) + return _coor + + +@fixture(scope="module") +def funcs(reg_contract): + _funcs = reg_contract.functions.__dict__ + del _funcs['_functions'] + del _funcs['abi'] + return _funcs + + +@fixture(scope="module") +def coor_funcs(coor): + _coor_funcs = coor.functions.__dict__ + del _coor_funcs['_functions'] + del _coor_funcs['abi'] + return _coor_funcs + + +def _ZapRegistry(reg_contract): + """ WIP: Returns an object representation of ZapRegistry for testing. + + This ZapRegistry object has a mocked BaseContract + used for its init phase. + + This mocked BaseContract has fixture data, including its contract. + + Contracts (and/or their functions) are asynchronous mock objects. + Almost everything else is base of Magic mock objects. + + """ + + mp = MonkeyPatch() + # same as setting the return_value of a mock obj + + class MockBaseContract: + def __init__(self, artifact_name, artifact_dir=None, network_id=None, + network_provider=None, coordinator=None, + address=None, web3=None): + + self.name = artifact_name + self.artifact = Artifacts[artifact_name] + coor_artifact = Artifacts["ZAPCOORDINATOR"] + self.provider = web3 or w3 + self.network_id = network_id or "1" + self.coordinator = self.provider.eth.contract( + abi=coor_artifact['abi'], + address=coor_artifact['networks'][self.network_id]['address']) + + self.address = self.artifact["networks"][self.network_id]["address"] + self.contract = reg_contract + + mp.setattr("Registry.registry.BaseContract", MockBaseContract) + from Registry.registry import ZapRegistry + + try: + return(ZapRegistry({"network_id": "31337"})) + except Exception as e: + raise e + + +@fixture(scope="module") +def Zap_Registry(): + """ yield a ZapRegistry object + + This object stays in pytest cache for the lifespan of the test. + It is not necessary to have a SetUp class method/function. + + references: + https://docs.pytest.org/en/stable/xunit_setup.html + https://docs.pytest.org/en/stable/fixture.html#what-fixtures-are + https://docs.pytest.org/en/stable/fixture.html#\ + fixtures-can-be-requested-more-than-once-per-test-return-values-are-cached + """ + zap_reg_obj = _ZapRegistry + yield zap_reg_obj + del zap_reg_obj + + +@fixture(scope="module") +def functions(reg_contract): + return reg_contract.functions + + +@fixture(scope="module") +def oracle(functions): + return functions.getOracleAddress(0).call() + + +@fixture(scope="module") +def account(): + return w3.eth.accounts[11] + + +@fixture(scope="module") +def curve_values(): + return [3, 0, 0, 3, 100000] + + +@fixture(scope="module") +def testZapProvider(curve_values): + _testZapProvider = { + "pubkey": 111, + "title": 'testProvider', + "endpoint_params": ['p1', 'p2'], + "endpoint": 'testEndpoint', + "query": 'btcPrice', + "curve": Curve(curve_values), + "broker": '0x0000000000000000000000000000000000000000' + } + + return _testZapProvider + + +@fixture(scope="module") +def endpoint(testZapProvider): + return testZapProvider["endpoint"] + + +@fixture(scope="module") +def endpoint_params(testZapProvider): + return testZapProvider["endpoint_params"] + + +@fixture(scope="module") +def broker(testZapProvider): + return testZapProvider["broker"] + + +@fixture(scope="module") +def pubkey(testZapProvider): + return testZapProvider["pubkey"] + + +@fixture(scope="module") +def title(testZapProvider): + return testZapProvider["title"] + + +@fixture(scope="module") +def anyio_backend(): + """ Ensures anyio uses the default, pytest-asyncio plugin + for running async tests + """ + return 'asyncio' + + +@fixture(scope="class") +def instance(Zap_Registry, reg_contract): + """ SetUp: ZapRegistry instance. + + This instance is cached and can be reused for + the remainder of the test. + """ + instance = Zap_Registry(reg_contract) + return instance diff --git a/tests/Registry/test_registry.py b/tests/Registry/test_registry.py index a34e22bb..83edc9e7 100644 --- a/tests/Registry/test_registry.py +++ b/tests/Registry/test_registry.py @@ -1,4 +1,4 @@ -""" Tests the setting up, transactions and calls of the Zap Registry contract. +""" Tests the setting up, transactions, and calls of the Zap Registry contract. This is an integration test that uses the Zap Hardhat testing environment to test the output and side effects of Zap Registry. @@ -10,173 +10,16 @@ Fixtures are ran once the first time they're requested and their return values are cached for the remainder of the test. """ -from pytest import ( - MonkeyPatch, raises, fixture, mark -) +from pytest import fixture, mark from web3 import Web3 from os.path import join, realpath -import sys -from subprocess import run -sys.path.insert(0, realpath(join(__file__, "../../../src/"))) +from sys import path +path.insert(0, realpath(join(__file__, "../../../src/"))) - -from Artifacts.src.index import Artifacts -from Types.types import const, txid from ZapToken.Curve.curve import Curve - - -# @fixture -# def test_provider(): -# return EthereumTesterProvider() - - -# @fixture -# def tester(test_provider): -# return test_provider.ethereum_tester - - -# @fixture -# def w3(test_provider): -# return Web3(test_provider) - -# @fixture(autouse=True) -# def prepare_hh(): -# run(["wsl", "-d Ubuntu", "-e", "killall node"], shell=True) -# run(["wsl", "-d Ubuntu", "-e", -# "~/projects/zap-hardhat/start.sh"], shell=True) - - -w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) -abi = Artifacts["REGISTRY"] -coor_artifact = Artifacts["ZAPCOORDINATOR"] -_address = abi["networks"]["31337"]["address"] - - -@fixture -def reg_contract(): - _reg_contract = w3.eth.contract(address=_address, - abi=abi["abi"]) - return _reg_contract - - -@fixture -def coor(): - _coor = w3.eth.contract(abi=coor_artifact['abi'], - address=coor_artifact['networks']["31337"]['address']) - return _coor - - -@fixture -def funcs(reg_contract): - _funcs = reg_contract.functions.__dict__ - del _funcs['_functions'] - del _funcs['abi'] - return _funcs - - -@fixture -def coor_funcs(coor): - _coor_funcs = coor.functions.__dict__ - del _coor_funcs['_functions'] - del _coor_funcs['abi'] - return _coor_funcs - - -""" Arrange/SetUp Section -""" - - -def _ZapRegistry(reg_contract): - """ WIP: Returns an object representation of ZapRegistry for testing. - - This ZapRegistry object has a mocked BaseContract - used for its init phase. - - This mocked BaseContract has fixture data, including its contract. - - Contracts (and/or their functions) are asynchronous mock objects. - Almost everything else is base of Magic mock objects. - - """ - - mp = MonkeyPatch() - # same as setting the return_value of a mock obj - - class MockBaseContract: - def __init__(self, artifact_name, artifact_dir=None, network_id=None, - network_provider=None, coordinator=None, - address=None, web3=None): - - self.name = artifact_name - self.artifact = Artifacts[artifact_name] - coor_artifact = Artifacts["ZAPCOORDINATOR"] - self.provider = web3 or w3 - self.network_id = network_id or "1" - self.coordinator = self.provider.eth.contract(abi=coor_artifact['abi'], - address=coor_artifact['networks'][self.network_id]['address']) - self.address = self.artifact["networks"][self.network_id]["address"] - self.contract = reg_contract - - mp.setattr("Registry.registry.BaseContract", MockBaseContract) - from Registry.registry import ZapRegistry - - try: - return(ZapRegistry({"network_id": "31337"})) - except Exception as e: - raise e - - -@fixture() -def Zap_Registry(): - """ yield a ZapRegistry object - - This object stays in pytest cache for the lifespan of the test. - It is not necessary to have a SetUp class method/function. - - references: - https://docs.pytest.org/en/stable/xunit_setup.html - https://docs.pytest.org/en/stable/fixture.html#what-fixtures-are - https://docs.pytest.org/en/stable/fixture.html#\ - fixtures-can-be-requested-more-than-once-per-test-return-values-are-cached - """ - zap_reg_obj = _ZapRegistry - yield zap_reg_obj - del zap_reg_obj - - -@fixture -def functions(reg_contract): - return reg_contract.functions - - -# @fixture -# def all_oracles(functions): -# return functions.getAllOracles().call() - - -@fixture -def oracle(functions): - return functions.getOracleAddress(0).call() - - -@fixture -def account(): - return w3.eth.accounts[11] - - -@fixture -def pubkey(functions, account): - return functions.getPublicKey(account).call() - - -@fixture -def anyio_backend(): - """ Ensures anyio uses the default, pytest-asyncio plugin - for running async tests - """ - return 'asyncio' +from Artifacts.src.index import Artifacts """ pytest section @@ -187,16 +30,6 @@ class TestRegistry: """ Tests the initialization and functionality of Zap's Registry Contract """ - @fixture - def instance(self, Zap_Registry, reg_contract): - """ SetUp: ZapRegistry instance. - - This instance is cached and can be reused for - the remainder of the test. - """ - instance = Zap_Registry(reg_contract) - return instance - def test_init(self, instance, funcs, coor_funcs): """ Test if ZapRegistry is initialized @@ -223,8 +56,8 @@ def test_init(self, instance, funcs, coor_funcs): # @mark.skip(reason="Provider already initialized.") @mark.anyio - async def test_initiate_provider(self, instance, pubkey, account): - opt = {"public_key": pubkey, "title": "Registry", + async def test_initiate_provider(self, instance, pubkey, account, title): + opt = {"public_key": pubkey, "title": title, "From": account, "gas": 4 * 10**5} tx = await instance.initiate_provider(**opt) @@ -296,9 +129,10 @@ async def get_all_provider_params(self, instance, address, pubkey): # @mark.skip(reason="Possible error with python types") @mark.anyio - async def test_initiate_provider_curve(self, instance, account): - term = [3, 0, 0, 3, 100000] - opts = {"end_point": 'hi', "term": term, + async def test_initiate_provider_curve(self, instance, account, + endpoint, curve_values): + term = curve_values + opts = {"end_point": endpoint, "term": term, "From": account, "gasPrice": int(5e4)} curve = await instance.initiate_provider_curve(**opts) @@ -322,29 +156,54 @@ async def test_get_provider_endpoints(self, instance, account): return e - @fixture - async def endpoints(self, instance, account): - e = await instance.get_provider_endpoints(account) - - return e - @mark.anyio async def test_get_provider_curve(self, instance, - account, endpoints): - e = endpoints - curve = await instance.get_provider_curve(account, e[-1]) + account, endpoint): + e = endpoint + curve = await instance.get_provider_curve(account, e) assert curve print(curve.values) assert isinstance(curve, Curve) + def decode_params(self, instance, params): + return(instance.decode_params(params)) + + def encode_params(self, instance, params): + return(instance.encode_params(params)) + + @mark.anyio + async def test_set_endpoint_params(self, instance, account, + endpoint, endpoint_params): + tx = await instance.set_endpoint_params( + endpoint, account, + int(5e4), + endpoint_params=endpoint_params) + + assert tx + assert isinstance(tx, str) + + @mark.anyio + async def test_get_endpoint_broker(self, instance, broker): + broker = await instance.get_endpoint_broker(broker, "123") + + assert broker + assert isinstance(broker, str) + + @mark.anyio + async def test_is_endpoint_set(self, instance, account, endpoint): + isSet = await instance.is_endpoint_set(account, endpoint) + + assert isSet is True + assert isinstance(isSet, bool) + @mark.anyio async def test_clear_endpoint(self, instance, - endpoints, + endpoint, account): - e = endpoints + e = endpoint tx = await instance.clear_endpoint( - e[-1], account, int(5e3)) + e, account, int(5e3)) assert tx print(tx) @@ -359,10 +218,10 @@ async def test_clear_endpoint(self, instance, # assert isinstance(get, bytes) -async def task(co_mock): - """ Will handle awaitables from contract calls/tx - """ - await co_mock +# async def task(co_mock): +# """ Will handle awaitables from contract calls/tx +# """ +# await co_mock """ python "direct calls" section """ From 0642a93a7d5228a8209cbf4612b1fb379713f984 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 15 Mar 2021 15:36:19 -0400 Subject: [PATCH 11/20] added asyncio.sleep calls to event listeners --- src/Registry/registry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Registry/registry.py b/src/Registry/registry.py index 2c6986a0..fa944d2d 100644 --- a/src/Registry/registry.py +++ b/src/Registry/registry.py @@ -345,12 +345,15 @@ async def is_endpoint_set(self, provider: address, endpoint: str): from typing import Callable async def listen(self, callback: Callable[..., None]): + sleep(3) self.contract.events.allEvents(callback) async def listen_new_provider(self, callback: TransactionCallback, filters: Filter = {}): + sleep(2) self.contract.events.NewProvider(filters, callback) async def listen_new_curve(self, callback: TransactionCallback, filters: Filter): + sleep(2) self.contract.events.NewCurve(filters, callback) From db23c391556b19e1ae16709d7934e4ce8dff0d71 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 22 Mar 2021 06:17:54 -0400 Subject: [PATCH 12/20] updated gitignore; added gitignore to tests and tests/registry root --- .gitignore | 2 ++ tests/.gitignore | 1 + tests/Registry/.gitignore | 1 + 3 files changed, 4 insertions(+) create mode 100644 tests/.gitignore create mode 100644 tests/Registry/.gitignore diff --git a/.gitignore b/.gitignore index 4b2cb76c..ca39a24b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ develop-eggs lib lib64 venv* +PRs +test_out # Installer logs pip-log.txt diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..f9f034f7 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +w3_playground.py \ No newline at end of file diff --git a/tests/Registry/.gitignore b/tests/Registry/.gitignore new file mode 100644 index 00000000..61a58b02 --- /dev/null +++ b/tests/Registry/.gitignore @@ -0,0 +1 @@ +test.sh \ No newline at end of file From d13b49d067afb57967a1480489f2f4dad075065b Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 22 Mar 2021 06:23:17 -0400 Subject: [PATCH 13/20] Utilised python packaging for contracts; All contract index/main files are renamed from `Contract/contract.py` to `contact/__init__.py`. Thereby changing how they are imported: from: ``` import Contract.contract ``` to: ``` import contract ``` --- src/Artifacts/src/{index.py => __init__.py} | 0 src/Registry/__init__.py | 0 src/ZapToken/Curve/__init__.py | 0 .../__init__.py} | 2 +- .../test/__init__.py | 0 .../test/test_base_contract.py | 0 src/{BaseContract => base_contract}/utils.py | 0 .../bondage.py => bondage/__init__.py} | 0 .../registry.py => registry/__init__.py} | 8 +- src/subscriber/__init__.py | 168 ++++++++++++++++++ .../Zaptoken.py => zap_token/__init__.py} | 19 +- .../curve.py => zap_token/curve/__init__.py} | 0 src/{ZapToken/Curve => zap_token/curve}/regex | 0 .../Curve => zap_token/curve}/types.py | 0 .../test/zaptoken_test.py | 0 src/{Types/types.py => zaptypes/__init__.py} | 0 16 files changed, 181 insertions(+), 16 deletions(-) rename src/Artifacts/src/{index.py => __init__.py} (100%) delete mode 100644 src/Registry/__init__.py delete mode 100644 src/ZapToken/Curve/__init__.py rename src/{BaseContract/base_contract.py => base_contract/__init__.py} (98%) 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 src/{Bondage/bondage.py => bondage/__init__.py} (100%) rename src/{Registry/registry.py => registry/__init__.py} (98%) create mode 100644 src/subscriber/__init__.py rename src/{ZapToken/Zaptoken.py => zap_token/__init__.py} (80%) rename src/{ZapToken/Curve/curve.py => zap_token/curve/__init__.py} (100%) rename src/{ZapToken/Curve => zap_token/curve}/regex (100%) rename src/{ZapToken/Curve => zap_token/curve}/types.py (100%) rename src/{ZapToken => zap_token}/test/zaptoken_test.py (100%) rename src/{Types/types.py => zaptypes/__init__.py} (100%) diff --git a/src/Artifacts/src/index.py b/src/Artifacts/src/__init__.py similarity index 100% rename from src/Artifacts/src/index.py rename to src/Artifacts/src/__init__.py diff --git a/src/Registry/__init__.py b/src/Registry/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/ZapToken/Curve/__init__.py b/src/ZapToken/Curve/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/BaseContract/base_contract.py b/src/base_contract/__init__.py similarity index 98% rename from src/BaseContract/base_contract.py rename to src/base_contract/__init__.py index fde613e6..7c866876 100644 --- a/src/BaseContract/base_contract.py +++ b/src/base_contract/__init__.py @@ -1,4 +1,4 @@ -import utils +# import utils from web3 import Web3 class BaseContract: 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/src/Bondage/bondage.py b/src/bondage/__init__.py similarity index 100% rename from src/Bondage/bondage.py rename to src/bondage/__init__.py diff --git a/src/Registry/registry.py b/src/registry/__init__.py similarity index 98% rename from src/Registry/registry.py rename to src/registry/__init__.py index fa944d2d..ad88956e 100644 --- a/src/Registry/registry.py +++ b/src/registry/__init__.py @@ -1,10 +1,10 @@ from web3 import Web3 from asyncio import sleep -from typing import Optional, List, Dict +from typing import Optional, List -from BaseContract.base_contract import BaseContract -from ZapToken.Curve.curve import Curve -from Types.types import ( +from base_contract import BaseContract +from zap_token.curve import Curve +from zaptypes import ( Filter, address, NetworkProviderOptions, const, TransactionCallback, txid ) diff --git a/src/subscriber/__init__.py b/src/subscriber/__init__.py new file mode 100644 index 00000000..17034964 --- /dev/null +++ b/src/subscriber/__init__.py @@ -0,0 +1,168 @@ +from web import Web3 +from asyncio import sleep + +from BaseContract.base_contract import BaseContract +from registry import ZapRegistry +from dispatch import ZapDispatch +from bondage import ZapBondage +from arbiter import ZapArbiter +from zaptoken import ZapToken + +from typing import Any + +from zaptypes import ( + NetworkProviderOptions, const, + TransactionCallback +) + + +class ZapSubscriber: + """ Represents an offchain Subscriber. + + Also provides an interface to the appropriate smart contracts. + """ + + def __init__(self, owner: str, + options: NetworkProviderOptions): + assert owner, "owner address is required" + self.subscriber_owner = owner + self.zap_token = ZapToken(**options) + self.zap_dispatch = ZapDispatch(**options) + self.zap_bondage = ZapBondage(**options) + self.zap_arbiter = ZapArbiter(**options) + self.zap_registry = ZapRegistry(**options) + + async def get_zap_balance(self) -> str or int: + """ Gets the Zap balance of the current ZapSubscriber. + + :return a Future that will resolve into the Zap Balance in wei. + """ + await sleep() + return self.zap_token.balance_of(self.subscriber_owner) + + async def get_zap_allowance(self) -> int or float: + """ Gets the Zap allowance of the current ZapSubscriber to Bondage. + + :return a Future that will resolve into the Zap Allowance in wei + """ + await sleep() + return self.zap_token.contract.methods.allowance( + self.subscriber_owner, + self.zap_bondage.methods) + + async def appove_to_bond(self, provider, zap_num, + gas_price, gas: const.DEFAULT_GAS) -> None: + """ Approve number of zap to a provider + + :param str provider : Provider's address + :param int zap_num : Number of Zap to approve + :param int gas : Number of gas limit + """ + await sleep() + return self.zap_token.approve({ + "to": self.zap_bondage.contract._address, + "amount": zap_num, + "gas": gas, + "gapPrice": gas_price + }) + + async def bond(self, provider, endpoint, + dots, gas_price, gas: const.DEFAULT_GAS, + cb: TransactionCallback = None) -> str: + """ Bonds `zap_num` amount of Zap to the given provider's endpoint, + yielding dots that enable this subscriber to send queries. + + :param str provider: Provider's address + :param str endpoint : Endpoint that this client wants to query from + :param int dots : Amount of dots to bond + :param class:`TransactionCallback` cb : Callback for transactionHash event + :returns str a Future that will eventually resolve into a transaction hash + """ + await sleep() + approved: int =\ + self.zap_token.contract.methods.allowance( + self.subscriber_owner, + self.zap_bondage.contract._address.call()) + + required: int = await\ + self.zap_bondage.calc_zap_for_dots(provider, endpoint, dots) + await sleep() + zap_balance = await self.get_zap_balance() + + assert (approved >= required), "You don\'t have enough ZAP approved." + assert (zap_balance >= required), "Balance insufficent." + + bonded = await self.zap_bondage.bond( + provider, endpoint, + dots, gas, + gas_price, From=self.subscriber_owner, + cb=cb) + + return bonded + + async def delegate_bond(self, provider, subscriber, + endpoint, dots, + cb: TransactionCallback = None) -> Any: + """ Delegate bond zapNum amount of Zap to provider's endpoint. + + Thereby yielding dots that enable the given subscriber + to send queries. + + :param address provider: Provider's address + :param address subscriber: subscriber's address that will bond with provider's endpoint + :param string endpoint: Endpoint that this client wants to query from + :param number dots: Amount of dots to bond + :param TransactionCallback cb: Callback for transactionHash event + :return Future that will resolve into a tx_hash + """ + await sleep() + approved: int =\ + self.zap_token.contract.methods.allowance( + self.subscriber_owner, + self.zap_bondage.contract._address.call()) + + required: int = await\ + self.zap_bondage.calc_zap_for_dots(provider, endpoint, dots) + await sleep() + zap_balance = await self.get_zap_balance() + + assert (approved >= required), "You don\'t have enough ZAP approved." + assert (zap_balance >= required), "Balance insufficent." + + bonded = await self.zap_bondage.bond( + provider, endpoint, + dots, subscriber, gas, + gas_price, From=self.subscriber_owner, + cb=cb) + + return bonded + + async def unbond(self, provider, endpoint, + dots, gas_price, + gas=const.DEFAULT_GAS, + cb: TransactionCallback = None) -> None: + """ Unbonds a given number of dots from a given oracle. + + Returns Zap to this subscriber based on the bonding curve. + + :param UnbondType u. {provider, endpoint, dots} + :param str provider - Oracle's address + :param str endpoint - Endpoint that the client has already bonded to + :param str or int u.dots - Number of dots to unbond (redeem) from this provider and endpoint + :param class:`TransactionCallback` cb - Callback for transactionHash event + :returns {Promise} Transaction hash + """ + await sleep() + return None + + async def function(self) -> None: + """ + """ + await sleep() + return None + + async def function(self) -> None: + """ + """ + await sleep() + return None diff --git a/src/ZapToken/Zaptoken.py b/src/zap_token/__init__.py similarity index 80% rename from src/ZapToken/Zaptoken.py rename to src/zap_token/__init__.py index 2f24b2b9..abd48b75 100644 --- a/src/ZapToken/Zaptoken.py +++ b/src/zap_token/__init__.py @@ -1,16 +1,18 @@ from base_contract import BaseContract -from BaseContract import utils -from portedFiles.types import (TransferType, address, txid, NetworkProviderOptions, TransactionCallback, NumType) -#from web3._utils. import (toHex) +from base_contract import utils +# from .curve import Curve +from zaptypes import ( + TransferType, address, txid, NetworkProviderOptions, TransactionCallback, NumType) +# from web3._utils. import (toHex) -class ZapToken(BaseConstract): +class ZapToken(BaseContract): def __init__(self, NetworkProviderOptions): - super().__init__(NetworkProviderOptions or {}, { artifactName: 'ZAP_Token'}) + super().__init__(NetworkProviderOptions or {}, {artifactName: 'ZAP_Token'}) async def balanceOf(self, address: address): return await self.contract.balanceof(adddress) - + # async def send({to, amount,from, gasPrice, gas = Util.DEFAULT_GAS } : TransferType, cb = TransactionCallback): # amount = toHex(amount) # promiEvent = self.contract.transfer(to, amount),send({from, gas, gasPrice}) @@ -39,8 +41,3 @@ async def balanceOf(self, address: address): # raise Exception('Failed to approve Bondage transfer') # return success - - - - - diff --git a/src/ZapToken/Curve/curve.py b/src/zap_token/curve/__init__.py similarity index 100% rename from src/ZapToken/Curve/curve.py rename to src/zap_token/curve/__init__.py diff --git a/src/ZapToken/Curve/regex b/src/zap_token/curve/regex similarity index 100% rename from src/ZapToken/Curve/regex rename to src/zap_token/curve/regex diff --git a/src/ZapToken/Curve/types.py b/src/zap_token/curve/types.py similarity index 100% rename from src/ZapToken/Curve/types.py rename to src/zap_token/curve/types.py diff --git a/src/ZapToken/test/zaptoken_test.py b/src/zap_token/test/zaptoken_test.py similarity index 100% rename from src/ZapToken/test/zaptoken_test.py rename to src/zap_token/test/zaptoken_test.py diff --git a/src/Types/types.py b/src/zaptypes/__init__.py similarity index 100% rename from src/Types/types.py rename to src/zaptypes/__init__.py From 0bf8c6a9914b9e8e8088775752025809c7f50a64 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 22 Mar 2021 06:26:51 -0400 Subject: [PATCH 14/20] revert subscriber for later PR --- src/subscriber/__init__.py | 168 ------------------------------------- 1 file changed, 168 deletions(-) delete mode 100644 src/subscriber/__init__.py diff --git a/src/subscriber/__init__.py b/src/subscriber/__init__.py deleted file mode 100644 index 17034964..00000000 --- a/src/subscriber/__init__.py +++ /dev/null @@ -1,168 +0,0 @@ -from web import Web3 -from asyncio import sleep - -from BaseContract.base_contract import BaseContract -from registry import ZapRegistry -from dispatch import ZapDispatch -from bondage import ZapBondage -from arbiter import ZapArbiter -from zaptoken import ZapToken - -from typing import Any - -from zaptypes import ( - NetworkProviderOptions, const, - TransactionCallback -) - - -class ZapSubscriber: - """ Represents an offchain Subscriber. - - Also provides an interface to the appropriate smart contracts. - """ - - def __init__(self, owner: str, - options: NetworkProviderOptions): - assert owner, "owner address is required" - self.subscriber_owner = owner - self.zap_token = ZapToken(**options) - self.zap_dispatch = ZapDispatch(**options) - self.zap_bondage = ZapBondage(**options) - self.zap_arbiter = ZapArbiter(**options) - self.zap_registry = ZapRegistry(**options) - - async def get_zap_balance(self) -> str or int: - """ Gets the Zap balance of the current ZapSubscriber. - - :return a Future that will resolve into the Zap Balance in wei. - """ - await sleep() - return self.zap_token.balance_of(self.subscriber_owner) - - async def get_zap_allowance(self) -> int or float: - """ Gets the Zap allowance of the current ZapSubscriber to Bondage. - - :return a Future that will resolve into the Zap Allowance in wei - """ - await sleep() - return self.zap_token.contract.methods.allowance( - self.subscriber_owner, - self.zap_bondage.methods) - - async def appove_to_bond(self, provider, zap_num, - gas_price, gas: const.DEFAULT_GAS) -> None: - """ Approve number of zap to a provider - - :param str provider : Provider's address - :param int zap_num : Number of Zap to approve - :param int gas : Number of gas limit - """ - await sleep() - return self.zap_token.approve({ - "to": self.zap_bondage.contract._address, - "amount": zap_num, - "gas": gas, - "gapPrice": gas_price - }) - - async def bond(self, provider, endpoint, - dots, gas_price, gas: const.DEFAULT_GAS, - cb: TransactionCallback = None) -> str: - """ Bonds `zap_num` amount of Zap to the given provider's endpoint, - yielding dots that enable this subscriber to send queries. - - :param str provider: Provider's address - :param str endpoint : Endpoint that this client wants to query from - :param int dots : Amount of dots to bond - :param class:`TransactionCallback` cb : Callback for transactionHash event - :returns str a Future that will eventually resolve into a transaction hash - """ - await sleep() - approved: int =\ - self.zap_token.contract.methods.allowance( - self.subscriber_owner, - self.zap_bondage.contract._address.call()) - - required: int = await\ - self.zap_bondage.calc_zap_for_dots(provider, endpoint, dots) - await sleep() - zap_balance = await self.get_zap_balance() - - assert (approved >= required), "You don\'t have enough ZAP approved." - assert (zap_balance >= required), "Balance insufficent." - - bonded = await self.zap_bondage.bond( - provider, endpoint, - dots, gas, - gas_price, From=self.subscriber_owner, - cb=cb) - - return bonded - - async def delegate_bond(self, provider, subscriber, - endpoint, dots, - cb: TransactionCallback = None) -> Any: - """ Delegate bond zapNum amount of Zap to provider's endpoint. - - Thereby yielding dots that enable the given subscriber - to send queries. - - :param address provider: Provider's address - :param address subscriber: subscriber's address that will bond with provider's endpoint - :param string endpoint: Endpoint that this client wants to query from - :param number dots: Amount of dots to bond - :param TransactionCallback cb: Callback for transactionHash event - :return Future that will resolve into a tx_hash - """ - await sleep() - approved: int =\ - self.zap_token.contract.methods.allowance( - self.subscriber_owner, - self.zap_bondage.contract._address.call()) - - required: int = await\ - self.zap_bondage.calc_zap_for_dots(provider, endpoint, dots) - await sleep() - zap_balance = await self.get_zap_balance() - - assert (approved >= required), "You don\'t have enough ZAP approved." - assert (zap_balance >= required), "Balance insufficent." - - bonded = await self.zap_bondage.bond( - provider, endpoint, - dots, subscriber, gas, - gas_price, From=self.subscriber_owner, - cb=cb) - - return bonded - - async def unbond(self, provider, endpoint, - dots, gas_price, - gas=const.DEFAULT_GAS, - cb: TransactionCallback = None) -> None: - """ Unbonds a given number of dots from a given oracle. - - Returns Zap to this subscriber based on the bonding curve. - - :param UnbondType u. {provider, endpoint, dots} - :param str provider - Oracle's address - :param str endpoint - Endpoint that the client has already bonded to - :param str or int u.dots - Number of dots to unbond (redeem) from this provider and endpoint - :param class:`TransactionCallback` cb - Callback for transactionHash event - :returns {Promise} Transaction hash - """ - await sleep() - return None - - async def function(self) -> None: - """ - """ - await sleep() - return None - - async def function(self) -> None: - """ - """ - await sleep() - return None From 4fed1c7c876c7d04c89c4ea8f0e930bacbae970b Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Tue, 30 Mar 2021 17:54:10 -0400 Subject: [PATCH 15/20] Fixed Hardhat ABI issues --- src/Artifacts/contracts/Arbiter.json | 2 +- src/Artifacts/contracts/Bondage.json | 2 +- src/Artifacts/contracts/Dispatch.json | 2 +- src/Artifacts/contracts/ZapToken.json | 6 ++++++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Artifacts/contracts/Arbiter.json b/src/Artifacts/contracts/Arbiter.json index 36448f38..b70befb1 100644 --- a/src/Artifacts/contracts/Arbiter.json +++ b/src/Artifacts/contracts/Arbiter.json @@ -419,7 +419,7 @@ "transactionHash": "0xd891f92a9be7d2cb45ff46bbdfdf1cf20cc9957a7f3137905e105abf24e01bc1" }, "31337": { - "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" + "address": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" } } } \ No newline at end of file diff --git a/src/Artifacts/contracts/Bondage.json b/src/Artifacts/contracts/Bondage.json index 3c6efc5e..d3c22075 100644 --- a/src/Artifacts/contracts/Bondage.json +++ b/src/Artifacts/contracts/Bondage.json @@ -749,7 +749,7 @@ "transactionHash": "0xbf5c11d55267fc39e8d3c7cb1b8aac179c2141ec7dd26a98d03017cd4676b79e" }, "31337": { - "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "address": "0x8a791620dd6260079bf849dc5567adc3f2fdc318" } } } \ No newline at end of file diff --git a/src/Artifacts/contracts/Dispatch.json b/src/Artifacts/contracts/Dispatch.json index 30c0ea92..3c10d742 100644 --- a/src/Artifacts/contracts/Dispatch.json +++ b/src/Artifacts/contracts/Dispatch.json @@ -771,7 +771,7 @@ "transactionHash": "0x9cbc488a7a4f2a217261e5f456f0118386a0a4b689e6c8ba8c601bda9341dd64" }, "31337": { - "address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + "address": "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707" } } } \ No newline at end of file diff --git a/src/Artifacts/contracts/ZapToken.json b/src/Artifacts/contracts/ZapToken.json index c6eb2c93..8e285a3b 100644 --- a/src/Artifacts/contracts/ZapToken.json +++ b/src/Artifacts/contracts/ZapToken.json @@ -422,6 +422,12 @@ "links": {}, "address": "0x11616e60d1b62633e02f044f155c9e03ff843974", "transactionHash": "0x995123b07bdd859f0edfef77c6f2842b8b32f57c586d591a6f1fff258b2c11f8" + }, + "31337": { + "events": {}, + "links": {}, + "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "transactionHash": "0xfa432264f28f3e8f3f151b9f834e1f6096d74b397bd5254cb883ef949aa5ad97" } } } \ No newline at end of file From dd327fae5f4c1da6ed7217cb140954dd9dde77c7 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Tue, 30 Mar 2021 18:09:47 -0400 Subject: [PATCH 16/20] Added Subscriber and Tests; Conftest added with fixtures for setUp and setUpClass procedures; `encode_params` added to test to encode params for query ; Tests incorporates event listening to validate certain results; --- src/subscriber/__init__.py | 356 +++++++++++++++++++++ tests/Subscriber/__init__.py | 0 tests/Subscriber/conftest.py | 243 ++++++++++++++ tests/Subscriber/encode_params/__init__.py | 25 ++ tests/Subscriber/test_subscriber.py | 302 +++++++++++++++++ 5 files changed, 926 insertions(+) create mode 100644 src/subscriber/__init__.py create mode 100644 tests/Subscriber/__init__.py create mode 100644 tests/Subscriber/conftest.py create mode 100644 tests/Subscriber/encode_params/__init__.py create mode 100644 tests/Subscriber/test_subscriber.py diff --git a/src/subscriber/__init__.py b/src/subscriber/__init__.py new file mode 100644 index 00000000..8903ea2c --- /dev/null +++ b/src/subscriber/__init__.py @@ -0,0 +1,356 @@ +from asyncio import sleep + +from registry import ZapRegistry +from dispatch import ZapDispatch +from bondage import ZapBondage +from arbiter import ZapArbiter +from zap_token import ZapToken + +from typing import Any, List, Optional + +from zaptypes import ( + NetworkProviderOptions, const, + TransactionCallback, txid, + address +) + + +class ZapSubscriber: + """ Represents an offchain Subscriber. + + Also provides an interface to the appropriate smart contracts. + """ + + def __init__(self, owner: str, + options: NetworkProviderOptions): + assert owner, "owner address is required" + self.subscriber_owner = owner + self.zap_token = ZapToken(**options) + self.zap_dispatch = ZapDispatch(**options) + self.zap_bondage = ZapBondage(**options) + self.zap_arbiter = ZapArbiter(**options) + self.zap_registry = ZapRegistry(**options) + + async def get_zap_balance(self) -> int: + """ Gets the Zap balance of the current ZapSubscriber. + + :return a Coroutine that will resolve into the Zap Balance in wei. + """ + balance: int = await\ + self.zap_token.balance_of(self.subscriber_owner) + return balance + + async def get_zap_allowance(self) -> int: + """ Gets the Zap allowance of the current ZapSubscriber to Bondage. + + :return a Coroutine that will resolve into the Zap Allowance in wei + """ + owner = self.zap_bondage.contract.address + + await sleep(1.5) + allowance: int = self.zap_token.contract.functions.allowance( + self.subscriber_owner, owner).call() + + return allowance + + async def approve_to_bond(self, zap_num: int, + From: address, gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS) -> txid: + """ Approve number of zap to a provider + + :param provider: Provider's address + + :param zap_num: Number of Zap to approve + + :param gas: Number of gas limit + """ + return await self.zap_token.approve( + self.zap_bondage.contract.address, + zap_num, + From, gas_price, gas) + + async def bond(self, provider: address, endpoint: str, dots: int, + From: address, gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS, + cb: Optional[TransactionCallback] = None) -> txid: + """ Bonds `zap_num` amount of Zap to the given provider's endpoint, + yielding dots that enable this subscriber to send queries. + + :param provider: Provider's address + + :param endpoint: Endpoint that this client wants to query from + + :param dots: Amount of dots to bond + + :param cb: Callback for transactionHash event + + :returns + a Coroutine that will eventually resolve + into a transaction hash + """ + await sleep(1.5) + approved: str =\ + self.zap_token.contract.functions.allowance( + self.subscriber_owner, + self.zap_bondage.contract.address).call() + + required: int = await\ + self.zap_bondage.calc_zap_for_dots( + provider, endpoint, dots) + + zap_balance = await self.get_zap_balance() + + assert (approved >= required),\ + "You don\'t have enough ZAP approved." + assert (zap_balance >= required),\ + "Balance insufficient." + + bonded = await self.zap_bondage.bond( + provider, endpoint, + dots, + From, gas_price, gas, + cb=cb) + + return bonded + + async def delegate_bond(self, provider: address, subscriber: address, + endpoint: str, dots: int, + From: address, gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS, + cb: Optional[TransactionCallback] = None) -> txid: + """ Delegate bond zapNum amount of Zap to provider's endpoint. + + Thereby yielding dots that enable the given subscriber + to send queries. + + :param provider: Provider's address + + :param subscriber: + subscriber's address that will bond with provider's endpoint + + :param endpoint: Endpoint that this client wants to query from + + :param dots: Amount of dots to bond + + :param cb: Callback for transactionHash event + + :return Coroutine that will resolve into a txid + """ + await sleep(1.5) + approved: int =\ + self.zap_token.contract.functions.allowance( + self.subscriber_owner, + self.zap_bondage.contract.address).call() + + required: int = await\ + self.zap_bondage.calc_zap_for_dots(provider, endpoint, dots) + zap_balance = await self.get_zap_balance() + + assert approved >= required,\ + "You don\'t have enough ZAP approved." + assert (zap_balance >= required),\ + "Balance insufficient." + + bonded = await self.zap_bondage.delegate_bond( + subscriber, endpoint, + dots, provider, From, + gas_price, gas, + cb=cb) + + return bonded + + async def unbond(self, provider: address, endpoint: address, dots: int, + From: address, gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS, + cb: Optional[TransactionCallback] = None) -> txid: + """ Unbonds a given number of dots from a given oracle. + + Returns Zap to this subscriber based on the bonding curve. + + :param provider: Oracle's address + + :param endpoint: Endpoint that the client has already bonded to + + :param dots: + Number of dots to unbond (redeem) from + this provider and endpoint + + :param cb: Callback for transactionHash event + + :returns a Coroutine that will resolve into a transaction hash + """ + bound_dots = await\ + self.zap_bondage.get_bound_dots( + self.subscriber_owner, + provider, + endpoint) + + assert (bound_dots >= dots),\ + "dots to unbond is less than requested" + + return await self.zap_bondage.unbond( + provider, endpoint, dots, + From, gas_price, gas, cb) + + async def subscribe(self, provider: address, endpoint: address, + endpoint_params: List[str], dots: int, + From: address, gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS, + cb: Optional[TransactionCallback] = None) -> str: + """ Initializes a temporal subscription to an oracle. + + Defined in terms of # of blocks. + + :param provider: Oracle's address + + :param endpoint: Endpoint that the client will query from + + :param endpointParams: The parameters passed to the oracle + + :param dots: + Number of dots to subscribe for, determining the number of + blocks this temporal subscription will last for + + :returns a Coroutine that will resolve into a transaction hash + """ + provider_pubkey = await\ + self.zap_registry.get_provider_publickey(provider) + zap_required = await\ + self.zap_bondage.calc_zap_for_dots(provider, endpoint, dots) + zap_balance = await\ + self.get_zap_balance() + + if zap_balance < zap_required: + raise Exception( + f"Insufficient balance; Requires {zap_required} Zap for {dots} dots") + + bound_dots = await\ + self.zap_bondage.get_bound_dots( + self.subscriber_owner, + provider, + endpoint) + + if bound_dots < dots: + raise Exception( + f"Insufficient bound dots; Please bond {dots} dots to subscribe") + blocks = dots + sub = await\ + self.zap_arbiter.initiate_subscription( + provider, endpoint, endpoint_params, + provider_pubkey, blocks, + From, gas_price, gas, cb) + + return sub + + async def query_data(self, provider: address, query: str, + endpoint: str, endpoint_params: List[str], + From: address, gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS, + cb: Optional[TransactionCallback] = None) -> txid: + """ Queries data from a subscriber to a given provider's endpoint. + + Parses a query string and endpoint parameters which will be + processed by the oracle. + + :param provider: Oracle's address + + :param query: Query string given to be handled by provider + + :param endpoint: + Data endpoint of provider, determines how query is handled + + :param endpointParams: + Parameters passed to data provider's endpoint + + :returns Coroutine Transaction hash + """ + bound_dots = await\ + self.zap_bondage.get_bound_dots( + self.subscriber_owner, + provider, endpoint) + + if not bound_dots: + raise Exception("Insufficient balance of bound dots to query") + + return\ + await self.zap_dispatch.query_data( + provider, query, endpoint, + endpoint_params, + From, gas_price, gas, cb) + + async def cancel_query(self, query_id, + From: address, gas_price: int, + gas: Optional[int] = const.DEFAULT_GAS, + cb: Optional[TransactionCallback] = None) -> txid: + """ Cancel a query_id + + :param queryId: string representation of the query id + """ + try: + await self.zap_dispatch.cancel_query( + query_id, + From, gas_price, gas, cb) + except Exception as e: + raise e + + async def get_num_escrow(self, provider: address, endpoint: str) -> int: + """ Get Number of dots in escrow + + :param provider + + :param endpoint + + :returns Number of escrow dots + """ + return\ + await self.zap_bondage.get_num_escrow( + provider, + self.subscriber_owner, + endpoint) + + async def get_bound_dots(self, provider: address, endpoint: str) -> int: + """ Gets the number of dots that are bounded to a provider's endpoint + for the current subscriber. + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :returns + a Coroutine that resolves into the number of bound dots + to this provider's endpoint + """ + return\ + await self.zap_bondage.get_bound_dots( + self.subscriber_owner, + provider, endpoint) + + async def listen_to_offchain_response( + self, + cb: Optional[TransactionCallback] = None, + Filter: Optional[dict] = {}) -> Any: + """ Listen to all Offchain responses events + + :param filter + + :param callback + """ + if not Filter["subscriber"]: + Filter.update({"subscriber": self.subscriber_owner}) + + self.zap_dispatch.listen_offchain_response(Filter, cb) + + """ Helpers + """ + async def hasEnoughZap(self, zap_required: int) -> bool: + """ Compares Zap Balance of Subscriber to a given amount. + + :param zap_required: Number of zap to check for + + :returns + a Coroutine that will resolve into a boolean + true if there is enough Zap + """ + balance: int = await\ + self.zap_token.balance_of(self.subscriber_owner) + return balance >= zap_required diff --git a/tests/Subscriber/__init__.py b/tests/Subscriber/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/Subscriber/conftest.py b/tests/Subscriber/conftest.py new file mode 100644 index 00000000..c9eb4812 --- /dev/null +++ b/tests/Subscriber/conftest.py @@ -0,0 +1,243 @@ +from pytest import MonkeyPatch, fixture +from unittest.mock import Mock + +from web3 import Web3 + +from os.path import join, realpath +from sys import path +path.insert(0, realpath(join(__file__, "../../../src/"))) + +from artifacts.src import Artifacts +from zap_token import Curve +from zaptypes import const + + +_w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) +abi_dir = {"registry": Artifacts["REGISTRY"], + "dispatch": Artifacts["DISPATCH"], + "bondage": Artifacts["BONDAGE"], + "arbiter": Artifacts["ARBITER"], + "zap_token": Artifacts["ZAP_TOKEN"] + } + + +@fixture(scope="module") +def w3(): + return _w3 + + +# @fixture(scope="module") +# def zt(): +# zt_abi = Artifacts["ZAP_TOKEN"] +# zt_address =\ +# _w3.toChecksumAddress( +# zt_abi["networks"]["development"]["address"]) + +# return (zt_abi, zt_address) + + +coor_artifact = Artifacts["ZAPCOORDINATOR"] +_address_dir = {contract: + Web3.toChecksumAddress( + abi["networks"]["31337"]["address"]) + for contract, abi in abi_dir.items()} + +""" Arrange/SetUp Section +""" + + +@fixture(scope="module") +def contracts(): + _contracts = {name: _w3.eth.contract(address=address, + abi=abi_dir[name]["abi"]) + for name, address in _address_dir.items()} + return _contracts + + +@fixture(scope="module") +def coor(): + _coor = _w3.eth.contract( + abi=coor_artifact['abi'], + address=coor_artifact['networks']["31337"]['address']) + return _coor + + +@fixture(scope="module") +def funcs(contracts): + _funcs = {} + for name, contract in contracts.items(): + _funcs[name] = contract.functions.__dict__ + del _funcs[name]['_functions'] + del _funcs[name]['abi'] + return _funcs + + +@fixture(scope="module") +def coor_funcs(coor): + _coor_funcs = coor.functions.__dict__ + del _coor_funcs['_functions'] + del _coor_funcs['abi'] + return _coor_funcs + + +@fixture(scope="module") +def accounts(): + return _w3.eth.accounts + + +@fixture(scope="class") +def owner(accounts): + return accounts[0] + + +@fixture(scope="class") +def subscriber(accounts): + return accounts[1] + + +@fixture(scope="class") +def oracle(accounts): + return accounts[2] + + +@fixture(scope="class") +def broker(accounts): + return accounts[3] + + +def _ZapSubscriber(contracts, funcs, subscriber): + + mp = MonkeyPatch() + + """ Mocked Contract classes + + These mocked objects have defined constants within this body + and will have variables to test various correct and erroneous + test cases within the test module. + + constants: + ContractClass.contact: the Solidty contract + """ + + # ZapBondage + mock_Bondage = Mock() + + # ZapToken + mock_ZapToken = Mock() + # print(mock_ZapToken.contract) + # mock_ZapToken.contract = + + mock_Dispatch = Mock() # ZapDispatch + mock_Arbiter = Mock() # ZapArbiter + mock_Registry = Mock() # ZapRegistry + + mp.setattr("subscriber.ZapBondage", mock_Bondage) + mp.setattr("subscriber.ZapToken", mock_ZapToken) + mp.setattr("subscriber.ZapDispatch", mock_Dispatch) + mp.setattr("subscriber.ZapArbiter", mock_Arbiter) + mp.setattr("subscriber.ZapRegistry", mock_Registry) + + # reminder: might need to run the Mock.configure_mock(**attrs) + # before running subscriber tests + + from subscriber import ZapSubscriber + + try: + return( + ZapSubscriber( + subscriber, + {"network_id.return_value": "31337"})) + except Exception as e: + raise e + + +@fixture(scope="module") +def Zap_Subscriber(): + zap_sub_obj = _ZapSubscriber + yield zap_sub_obj + del zap_sub_obj + + +@fixture(scope="module") +def hardhat_provider(): + return "http://localhost:8545" + + +@fixture(scope="module") +def provider(accounts): + return {"pubkey": 102, + "title": '0x426f72676573697573', + "address": accounts[2], + "endpoint_params": ['param1', 'param2'], + "endpoint": 'Wiles', + "query": 'btcPrice', + "curve": Curve([3, 0, 0, 1, 1222]), + "broker": '0x0000000000000000000000000000000000000000' + } + + +@fixture(scope="module") +def function(): + pass + + +@fixture(scope="module") +def anyio_backend(): + """ Ensures anyio uses the default, pytest-asyncio plugin + for running async tests + """ + return 'asyncio' + + +@fixture(scope="class") +def instance(Zap_Subscriber, contracts, funcs, subscriber): + return Zap_Subscriber(contracts, funcs, subscriber) + + +@fixture(scope="class", autouse=True) +def prepare_tokens(instance, + subscriber, owner, broker, w3): + bondage_owner = instance.zap_bondage.contract.address + instance.zap_token.contract.functions.allocate( + owner, 1500000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + instance.zap_token.contract.functions.allocate( + subscriber, 50000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_token.contract.functions.allocate( + broker, 50000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_token.contract.functions.approve( + bondage_owner, 1000000000000000000000000000000).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_token.contract.functions.approve( + bondage_owner, 1000000000000000000000000000000).transact( + {"from": broker, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + +# @fixture(scope="module") +# def function(): +# return None + + +# @fixture(scope="module") +# def function(): +# return None + + +# @fixture(scope="module") +# def function(): +# return None + + +# @fixture(scope="module") +# def function(): +# return None diff --git a/tests/Subscriber/encode_params/__init__.py b/tests/Subscriber/encode_params/__init__.py new file mode 100644 index 00000000..c5238bc8 --- /dev/null +++ b/tests/Subscriber/encode_params/__init__.py @@ -0,0 +1,25 @@ +from web3 import Web3 + + +def encode_params(self, endpoint_params: list = [], ): + """ needed for + """ + pars = endpoint_params + hex_params =\ + [el if el.find('0x') == 0 else Web3.toHex(text=el) for el in pars] + bytes_params =\ + [Web3.toBytes(hexstr=hex_p) for hex_p in hex_params] + params = [] + + from math import ceil + for element in bytes_params: + if len(element) <= 32: + params.append(Web3.toHex(element)) + continue + chunks_len = ceil((len(element) + 2) / 32) + param_bytes_w_len = [0, chunks_len].extend(element) + for i in range(0, chunks_len): + start = i * 32 + end = start + 32 + params.append(Web3.toHex(param_bytes_w_len[start:end])) + return params diff --git a/tests/Subscriber/test_subscriber.py b/tests/Subscriber/test_subscriber.py new file mode 100644 index 00000000..bb28fe8a --- /dev/null +++ b/tests/Subscriber/test_subscriber.py @@ -0,0 +1,302 @@ +from unittest.mock import AsyncMock +from pytest import mark, fixture + +from web3 import Web3 +from . import encode_params + +from os.path import join, realpath +from sys import path +path.insert(0, realpath(join(__file__, "../../../src/"))) + +from zaptypes import const + + +""" pytest section +""" + + +class TestSubscriber: + + query_id = "" + + global tx_meta + tx_meta = "" + + def test_init(self, instance): + assert instance + + """ contract patching + """ + + @fixture(scope="class", autouse=True) + def patch_contracts(self, instance, contracts, oracle): + instance.zap_registry.contract = contracts["registry"] + instance.zap_token.contract = contracts["zap_token"] + instance.zap_bondage.contract = contracts["bondage"] + instance.zap_dispatch.contract = contracts["dispatch"] + + @fixture(scope="class", autouse=True) + def patch_all_contract_functions(self, instance, subscriber, w3): + """ Patches all contract functions before tests + """ + + global tx_meta + + tx_meta = {"From": instance.subscriber_owner, + "gas_price": w3.eth.gas_price} + + """ ZapToken Functions + """ + + # ZapToken.balance_of + def side_effect(acc): + return\ + instance.zap_token.contract.functions.balanceOf( + acc).call() + + instance.zap_token.balance_of = AsyncMock() + instance.zap_token.balance_of.side_effect = side_effect + + """ Bondage Function + """ + + # Bondage.calc_zap_for_dots + def side_effect(prov, ep, dots): + return instance.zap_bondage.contract.functions.calcZapForDots( + prov, Web3.toBytes(text=ep), dots).call() + + instance.zap_bondage.calc_zap_for_dots = AsyncMock() + instance.zap_bondage.calc_zap_for_dots.side_effect = side_effect + + # Bondage.get_bound_dots + def side_effect(sub, prov, ep): + return instance.zap_bondage.contract.functions.getBoundDots( + sub, prov, Web3.toBytes(text=ep)).call() + + instance.zap_bondage.get_bound_dots = AsyncMock() + instance.zap_bondage.get_bound_dots.side_effect = side_effect + + # Bondage.bond + def side_effect(prov, ep, dts, From, gas_price, gas, cb=None): + return instance.zap_bondage.contract.functions.bond( + prov, Web3.toBytes(text=ep), int(dts)).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_bondage.bond = AsyncMock() + instance.zap_bondage.bond.side_effect = side_effect + + # Bondage.unbond + def side_effect(prov, ep, dts, From, gas_price, gas, cb=None): + return instance.zap_bondage.contract.functions.unbond( + prov, Web3.toBytes(text=ep), int(dts)).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_bondage.unbond = AsyncMock() + instance.zap_bondage.unbond.side_effect = side_effect + + @mark.anyio + async def test_get_zap_balance(self, instance): + + balance = await instance.get_zap_balance() + + assert isinstance(balance, int) + print("Subscriber Balance: ", balance) + + @mark.anyio + async def test_get_zap_allowance(self, instance, contracts): + allowance = await instance.get_zap_allowance() + + assert isinstance(allowance, int) + print("Subscriber allowance: ", allowance) + + @mark.skip("Already approving on SetUp") + @mark.anyio + async def test_approve_to_bond(self, instance, oracle, + w3, provider): + def side_effect(to, amount): + tx_hash = instance.zap_token.contract.functions.approve( + to, amount).transact().hex() + receipt = w3.eth.getTransactionReceipt(tx_hash) + log = instance.zap_token.contract.events.Approval( + ).processReceipt(receipt) + try: + return not log[0]["errors"] + except KeyError: + return True + + instance.zap_token.approve = AsyncMock() + instance.zap_token.approve.side_effect = side_effect + + approve = await\ + instance.approve_to_bond(1000000000) + + assert approve + + @mark.anyio + async def test_bond(self, instance, provider, + subscriber, oracle, broker): + + await\ + instance.bond( + oracle, + provider["endpoint"], + 999, **tx_meta) + + bonded = await instance.zap_bondage.get_bound_dots( + subscriber, + oracle, + provider["endpoint"]) + + assert bonded + assert bonded == 999 + + await\ + instance.unbond( + oracle, + provider["endpoint"], + 999, **tx_meta) + + @mark.anyio + async def test_subscribe(self, instance, oracle, provider, + subscriber, contracts, w3): + + def side_effect(prov): + return\ + instance.zap_registry.contract.functions.getProviderPublicKey( + prov).call() + + instance.zap_registry.get_provider_publickey = AsyncMock() + instance.zap_registry.get_provider_publickey.side_effect =\ + side_effect + + instance.zap_arbiter.contract = contracts["arbiter"] + + def side_effect(provider, endpoint, + endpoint_params, pubkey, + blocks, From, gas_price, gas, cb): + endpoint_params =\ + [Web3.toBytes(text=param) + if not isinstance(param, bytes) + else param + for param in endpoint_params] + return\ + instance.zap_arbiter.contract.functions.initiateSubscription( + provider, Web3.toBytes(text=endpoint), + endpoint_params, pubkey, blocks).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}).hex() + + instance.zap_arbiter.initiate_subscription = AsyncMock() + instance.zap_arbiter.initiate_subscription.side_effect =\ + side_effect + + bounded = await instance.zap_bondage.get_bound_dots( + subscriber, oracle, provider["endpoint"]) + + if bounded == 0: + await instance.bond( + oracle, + provider["endpoint"], + 100, **tx_meta) + + sub = await instance.subscribe(oracle, provider["endpoint"], + provider["endpoint_params"], 100, + **tx_meta) + + assert sub + + @mark.anyio + async def test_query_data(self, instance, w3, + oracle, provider, subscriber): + + global query_id + + def side_effect(prov, q, ep, ep_params, From, gas_price, gas, cb=None): + return\ + instance.zap_dispatch.contract.functions.query( + prov, q, Web3.toBytes(text=ep), ep_params).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_dispatch.query_data = AsyncMock() + instance.zap_dispatch.query_data.side_effect = side_effect + + params = encode_params.encode_params(provider["endpoint_params"]) + + bounded = await instance.zap_bondage.get_bound_dots( + subscriber, oracle, provider["endpoint"]) + + if bounded < 999: + await instance.bond( + oracle, + provider["endpoint"], + 999, + **tx_meta) + + query = await instance.query_data( + oracle, "query", provider["endpoint"], + params, + **tx_meta) + + assert query + + receipt = w3.eth.getTransactionReceipt(query) + query_id = instance.zap_dispatch.contract.events.Incoming( + ).processReceipt( + receipt)[0]["args"]["id"] + + @mark.anyio + async def test_cancel_query(self, instance, w3, subscriber): + + def side_effect(q_id, From, gas_price, gas, cb): + return\ + instance.zap_dispatch.contract.functions.cancelQuery( + int(q_id)).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_dispatch.cancel_query = AsyncMock() + instance.zap_dispatch.cancel_query.side_effect =\ + side_effect + + await instance.cancel_query(query_id, **tx_meta) + + @mark.anyio + async def test_delegate_bond(self, instance, w3, + provider, oracle, subscriber): + + def side_effect(sub, ep, dots, prov, From, gas_price, gas, cb=None): + return\ + instance.zap_bondage.contract.functions.delegateBond( + sub, prov, Web3.toBytes(text=ep), dots).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_bondage.delegate_bond = AsyncMock() + instance.zap_bondage.delegate_bond.side_effect =\ + side_effect + + del_bounded = await instance.delegate_bond( + oracle, instance.zap_bondage.contract.address, + provider["endpoint"], 1, **tx_meta) + + assert del_bounded + + @mark.anyio + async def test_get_num_escrow(self, instance, provider, oracle): + + def side_effect(provider, subscriber, endpoint): + return\ + instance.zap_bondage.contract.functions.getNumEscrow( + provider, subscriber, Web3.toBytes(text=endpoint)).call() + + instance.zap_bondage.get_num_escrow = AsyncMock() + instance.zap_bondage.get_num_escrow.side_effect =\ + side_effect + + escrowed = await instance.get_num_escrow( + oracle, provider["endpoint"]) + + assert isinstance(escrowed, int) From 70d97d8ea83815e539695a1442757bf34f6797bc Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Tue, 30 Mar 2021 19:51:42 -0400 Subject: [PATCH 17/20] prepare tokens autouse; --- tests/Subscriber/conftest.py | 29 ---------------------------- tests/Subscriber/test_subscriber.py | 30 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/tests/Subscriber/conftest.py b/tests/Subscriber/conftest.py index c9eb4812..81cbbd6b 100644 --- a/tests/Subscriber/conftest.py +++ b/tests/Subscriber/conftest.py @@ -194,35 +194,6 @@ def instance(Zap_Subscriber, contracts, funcs, subscriber): return Zap_Subscriber(contracts, funcs, subscriber) -@fixture(scope="class", autouse=True) -def prepare_tokens(instance, - subscriber, owner, broker, w3): - bondage_owner = instance.zap_bondage.contract.address - instance.zap_token.contract.functions.allocate( - owner, 1500000000000000000000000000000).transact( - {"from": owner, "gas": const.DEFAULT_GAS, - "gasPrice": w3.eth.gas_price}) - instance.zap_token.contract.functions.allocate( - subscriber, 50000000000000000000000000000).transact( - {"from": owner, "gas": const.DEFAULT_GAS, - "gasPrice": w3.eth.gas_price}) - - instance.zap_token.contract.functions.allocate( - broker, 50000000000000000000000000000).transact( - {"from": owner, "gas": const.DEFAULT_GAS, - "gasPrice": w3.eth.gas_price}) - - instance.zap_token.contract.functions.approve( - bondage_owner, 1000000000000000000000000000000).transact( - {"from": subscriber, "gas": const.DEFAULT_GAS, - "gasPrice": w3.eth.gas_price}) - - instance.zap_token.contract.functions.approve( - bondage_owner, 1000000000000000000000000000000).transact( - {"from": broker, "gas": const.DEFAULT_GAS, - "gasPrice": w3.eth.gas_price}) - - # @fixture(scope="module") # def function(): # return None diff --git a/tests/Subscriber/test_subscriber.py b/tests/Subscriber/test_subscriber.py index bb28fe8a..1b360152 100644 --- a/tests/Subscriber/test_subscriber.py +++ b/tests/Subscriber/test_subscriber.py @@ -14,7 +14,6 @@ """ pytest section """ - class TestSubscriber: query_id = "" @@ -96,6 +95,35 @@ def side_effect(prov, ep, dts, From, gas_price, gas, cb=None): instance.zap_bondage.unbond = AsyncMock() instance.zap_bondage.unbond.side_effect = side_effect + @fixture(scope="class", autouse=True) + def prepare_tokens(self, instance, + subscriber, owner, broker, w3): + bondage_owner = instance.zap_bondage.contract.address + instance.zap_token.contract.functions.allocate( + owner, 1500000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + instance.zap_token.contract.functions.allocate( + subscriber, 50000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_token.contract.functions.allocate( + broker, 50000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_token.contract.functions.approve( + bondage_owner, 1000000000000000000000000000000).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + instance.zap_token.contract.functions.approve( + bondage_owner, 1000000000000000000000000000000).transact( + {"from": broker, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + @mark.anyio async def test_get_zap_balance(self, instance): From 81b5729c6d245741139628af3504c58dfd93a570 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 3 May 2021 16:09:11 -0400 Subject: [PATCH 18/20] fixed tests --- src/bondage/__init__.py | 108 ++-------------------------- src/zap_token/__init__.py | 46 ++---------- tests/Subscriber/test_subscriber.py | 11 ++- 3 files changed, 21 insertions(+), 144 deletions(-) diff --git a/src/bondage/__init__.py b/src/bondage/__init__.py index 00573184..b5ea6f6f 100644 --- a/src/bondage/__init__.py +++ b/src/bondage/__init__.py @@ -1,104 +1,6 @@ -from BaseContract.base_contract import BaseContract -#from Registry.registry import Registry (in hopes this will get ported soon...) -from portedFiles.types import (BondageArgs, BondArgs, UnbondArgs, DelegateBondArgs, BondFilter, Filter, txid, NetworkProviderOptions, Constants, NumType, TransactionCallback) -from web3._utils import (utf8ToHex, toHex) - -class ZapBondage(BaseConstract): - - def __init__(self, NetworkProviderOptions): - super().__init__(self, NetworkProviderOptions or {}, { artifactName: 'Bondage'}) - - async def bond(self, bond_args: BondArgs , callback: TransactionCallback): - assert(dots and dots > 0, 'Dots to bond must be greater than 0.') - dots = toHex(bond_args['dots']) - #getEndpointBroker() is part of the Registry code (currently missing) - broker = await self.contract.getEndpointBroker(bond_args['provider'], utf8ToHex(bond_args['endpoint'])) - if broker != const.NULL_ADDRESS: - if bond_args['frm'] != broker: - raise Error('Broker address {broker} needs to call delegate bonding'.format(broker)) - promiEvent = self.contract.bond( - bond_args['provider'], - utf8ToHex(bond_args['endpoint']), - dots) - .send({ bond_args['frm'], bond_args['gas'], bond_args['gasPrice'] }) - if callback: - #Add events - pass - return promiEvent - - async def delegateBond(self, delegate_bond_args: DelegateBondArgs, callback: TransactionCallback): - assert(dots and dots > 0, 'Dots to bond must be greater than 0.') - dots = toHex(delegate_bond_args['dots']) - broker = await self.contract.getEndpointBroker(delegate_bond_args['provider'], utf8ToHex(delegate_bond_args['endpoint'])) - if broker != const.NULL_ADDRESS: - if delegate_bond_args['frm'] != broker: - raise Error('Broker address {broker} needs to call delegate bonding for this endpoint'.format(broker)) - promiEvent = self.contract.delegateBond( - delegate_bond_args['subscriber'], - delegate_bond_args['provider'], - utf8ToHex(delegate_bond_args['endpoint']), - dots) - .send({ delegate_bond_args['frm'], delegate_bond_args['gas'], delegate_bond_args['gasPrice'] }) - if callback: - #Add events - pass - return promiEvent - - async def unbond(self, unbond_args: UnbondArgs , callback: TransactionCallback): - assert(dots and dots > 0, 'Dots to unbond must be greater than 0.') - dots = toHex(unbond_args['dots']) - #getEndpointBroker() is part of the Registry code (currently missing) - broker = await self.contract.getEndpointBroker(unbond_args['provider'], utf8ToHex(unbond_args['endpoint'])) - if broker != const.NULL_ADDRESS: - if unbond_args['frm'] != broker: - raise Error('Broker address {broker} needs to call unbonding for this endpoint'.format(broker)) - promiEvent = self.contract.unbond( - unbond_args['provider'], - utf8ToHex(unbond_args['endpoint']), - dots) - .send({ unbond_args['frm'], unbond_args['gas'], unbond_args['gasPrice'] }) - if callback: - #Add events - pass - return promiEvent - - - #GETTERS - async def getBoundDots(self, bondage_args: BondageArgs): - return await self.contract.getBoundDots(bondage_args['subscriber'], bondage_args['provider'],utf8ToHex(bondage_args['endpoint'])) - - async def getDotsLimit(self, bondage_args: BondageArgs): - return await self.contract.dotLimit(bondage_args['provider'], utf8ToHex(bondage_args['endpoint'])) - - async def getDotsIssued(self, bondage_args: BondageArgs): - return await self.contract.getDotsIssued(bondage_args['provider'], utf8ToHex(bondage_args['endpoint'])) - - async def getBrokerAddress(self, bondage_args: BondageArgs): - return await self.contract.getEndpointBroker(bondage_args['provider'], utf8ToHex(bondage_args['endpoint'])) - - async def getZapBound(self, bondage_args: BondageArgs): - return await self.contract.getZapBound(bondage_args['provider'], utf8ToHex(bondage_args['endpoint'])) - - async def getNumEscrow(self, bondage_args: BondageArgs): - return await self.contract.getNumEscrow(bondage_args['subscriber'], bondage_args['provider'], bondage_args['endpoint']) - - - #EVENTS (Still not sure on these, I'm unaware of what the .events method is referring to) - def listen(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.allEvents(filters, { 'fromBlock': 0, 'toBlock': 'latest' }, callback) - - def listenBound(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Bound(filters, { 'toBlock': 'latest' }, callback) - - def listenBound(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Unbond(filters, {'toBlock': 'latest' }, callback) - - def listenEscrowed(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Escrowed(filters, { 'toBlock': 'latest' }, callback) - - def listenReleased(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Released(filters, {'toBlock': 'latest' }, callback) - - def listenReturned(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Returned(filters, { 'toBlock': 'latest' }, callback) +class ZapBondage: + """docstring for ZapBondage""" + def __init__(self, arg): + super(ZapBondage, self).__init__() + self.arg = arg diff --git a/src/zap_token/__init__.py b/src/zap_token/__init__.py index abd48b75..9e4698c7 100644 --- a/src/zap_token/__init__.py +++ b/src/zap_token/__init__.py @@ -1,43 +1,9 @@ -from base_contract import BaseContract -from base_contract import utils -# from .curve import Curve -from zaptypes import ( - TransferType, address, txid, NetworkProviderOptions, TransactionCallback, NumType) -# from web3._utils. import (toHex) +from .curve import Curve -class ZapToken(BaseContract): - def __init__(self, NetworkProviderOptions): - super().__init__(NetworkProviderOptions or {}, {artifactName: 'ZAP_Token'}) +class ZapToken: + """docstring for ZapToken""" - async def balanceOf(self, address: address): - return await self.contract.balanceof(adddress) - - # async def send({to, amount,from, gasPrice, gas = Util.DEFAULT_GAS } : TransferType, cb = TransactionCallback): - # amount = toHex(amount) - # promiEvent = self.contract.transfer(to, amount),send({from, gas, gasPrice}) - # if cb: - # #Event - # pass - # return promiEvent - - # async def allocate({to, amount,from, gasPrice, gas = Util.DEFAULT_GAS } : TransferType, cb = TransactionCallback): - # amount = toHex(amount) - # promiEvent = self.contract.allocate(to, amount),send({from, gas, gasPrice}) - # if cb: - # #Event - # pass - # return promiEvent - - # async def approve({to, amount,from, gasPrice, gas = Util.DEFAULT_GAS } : TransferType, cb = TransactionCallback): - # amount = toHex(amount) - # _success = self.contract.approve(to,ammount).send({from, gas, gasPrice}) - # if cd: - # #Event - # pass - # success = await _success - - # if (not success): - # raise Exception('Failed to approve Bondage transfer') - - # return success + def __init__(self, arg): + super(ZapToken, self).__init__() + self.arg = arg diff --git a/tests/Subscriber/test_subscriber.py b/tests/Subscriber/test_subscriber.py index 1b360152..84f4b82b 100644 --- a/tests/Subscriber/test_subscriber.py +++ b/tests/Subscriber/test_subscriber.py @@ -232,7 +232,7 @@ def side_effect(provider, endpoint, sub = await instance.subscribe(oracle, provider["endpoint"], provider["endpoint_params"], 100, **tx_meta) - + assert isinstance(sub, str) assert sub @mark.anyio @@ -310,7 +310,16 @@ def side_effect(sub, ep, dots, prov, From, gas_price, gas, cb=None): oracle, instance.zap_bondage.contract.address, provider["endpoint"], 1, **tx_meta) + instance.zap_bondage.functions.unbond + assert del_bounded + assert isinstance(del_bounded, int) + assert del_bounded > 0 + + tx_meta2 = tx_meta + tx_meta2["From"] = instance.zap_bondage.contract.address + instance.unbond( + oracle, provider["endpoint"], 1, **tx_meta2) @mark.anyio async def test_get_num_escrow(self, instance, provider, oracle): From 3aee2bf7a06081ebb124da9d740a569e0af021a3 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 3 May 2021 16:10:08 -0400 Subject: [PATCH 19/20] empty contract ports for mocking; --- src/arbiter/__init__.py | 6 ++++++ src/dispatch/__init__.py | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 src/arbiter/__init__.py create mode 100644 src/dispatch/__init__.py diff --git a/src/arbiter/__init__.py b/src/arbiter/__init__.py new file mode 100644 index 00000000..5bbe5d63 --- /dev/null +++ b/src/arbiter/__init__.py @@ -0,0 +1,6 @@ +class ZapArbiter: + """docstring for ZapArbiter""" + + def __init__(self, arg): + super(ZapArbiter, self).__init__() + self.arg = arg diff --git a/src/dispatch/__init__.py b/src/dispatch/__init__.py new file mode 100644 index 00000000..36ba3836 --- /dev/null +++ b/src/dispatch/__init__.py @@ -0,0 +1,6 @@ +class ZapDispatch: + """docstring for ZapDispatch""" + + def __init__(self, arg): + super(ZapDispatch, self).__init__() + self.arg = arg From 77f87ad2f37b6dd92e8a00505502d2e382b9cc78 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 3 May 2021 16:10:58 -0400 Subject: [PATCH 20/20] src/Artifacts -> src/artifacts --- src/{Artifacts => artifacts}/contracts/Arbiter.json | 2 +- src/{Artifacts => artifacts}/contracts/Bondage.json | 0 src/{Artifacts => artifacts}/contracts/Client1.json | 0 src/{Artifacts => artifacts}/contracts/Client2.json | 0 src/{Artifacts => artifacts}/contracts/Client3.json | 0 src/{Artifacts => artifacts}/contracts/Client4.json | 0 src/{Artifacts => artifacts}/contracts/ClientBytes32Array.json | 0 src/{Artifacts => artifacts}/contracts/ClientIntArray.json | 0 src/{Artifacts => artifacts}/contracts/CurrentCost.json | 0 src/{Artifacts => artifacts}/contracts/Dispatch.json | 0 src/{Artifacts => artifacts}/contracts/Piecewise.json | 0 src/{Artifacts => artifacts}/contracts/Registry.json | 0 src/{Artifacts => artifacts}/contracts/RegistryInterface.json | 0 src/{Artifacts => artifacts}/contracts/TokenDotFactory.json | 0 src/{Artifacts => artifacts}/contracts/TokenFactory.json | 0 src/{Artifacts => artifacts}/contracts/ZapCoordinator.json | 0 .../contracts/ZapCoordinatorInterface.json | 0 src/{Artifacts => artifacts}/contracts/ZapToken.json | 0 src/{Artifacts => artifacts}/src/__init__.py | 0 src/{Artifacts => artifacts}/src/tests/__init__.py | 0 src/{Artifacts => artifacts}/src/tests/test_artifacts.py | 0 21 files changed, 1 insertion(+), 1 deletion(-) rename src/{Artifacts => artifacts}/contracts/Arbiter.json (99%) 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/__init__.py (100%) rename src/{Artifacts => artifacts}/src/tests/__init__.py (100%) rename src/{Artifacts => artifacts}/src/tests/test_artifacts.py (100%) diff --git a/src/Artifacts/contracts/Arbiter.json b/src/artifacts/contracts/Arbiter.json similarity index 99% rename from src/Artifacts/contracts/Arbiter.json rename to src/artifacts/contracts/Arbiter.json index b70befb1..561d738d 100644 --- a/src/Artifacts/contracts/Arbiter.json +++ b/src/artifacts/contracts/Arbiter.json @@ -419,7 +419,7 @@ "transactionHash": "0xd891f92a9be7d2cb45ff46bbdfdf1cf20cc9957a7f3137905e105abf24e01bc1" }, "31337": { - "address": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + "address": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0" } } } \ No newline at end of file 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/__init__.py b/src/artifacts/src/__init__.py similarity index 100% rename from src/Artifacts/src/__init__.py rename to src/artifacts/src/__init__.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