From 5740b168b941aa914731b9322ba3dbbeb4ce532d Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 1 Mar 2021 11:21:27 -0400 Subject: [PATCH 01/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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 b9d17e541f5bd4fc61a8fd4e6279d6238f27637f Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Tue, 30 Mar 2021 18:11:30 -0400 Subject: [PATCH 16/17] Edited docstrings, fixed importing in tests; --- src/registry/__init__.py | 147 +++++++++++++++++--------------- tests/Registry/conftest.py | 10 +-- tests/Registry/test_registry.py | 8 +- 3 files changed, 89 insertions(+), 76 deletions(-) diff --git a/src/registry/__init__.py b/src/registry/__init__.py index ad88956e..a424bb2e 100644 --- a/src/registry/__init__.py +++ b/src/registry/__init__.py @@ -11,23 +11,27 @@ class ZapRegistry(BaseContract): - """This contract manages Providers and Curve registration + """ This contract manages Providers and Curve registration - NetworkProviderOptions -- Dictionary object containing options for + NetworkProviderOptions: Dictionary object containing options for BaseContract init - NetworkProviderOptions has the following keyword arguments: + 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) + :param arifactsDir: Directory where contract ABIs are located + + :param networkId: + Select which network the contract is located + options - (mainnet, testnet, private) + + :param networkProvider: + Ethereum network provider (e.g. Infura or web3) Example: ZapRegistry({"networkId": 42, "networkProvider": "web3"}) """ - def __init__(self, options: NetworkProviderOptions = {}): + def __init__(self, options: Optional[NetworkProviderOptions] = {}): options["artifact_name"] = "REGISTRY" BaseContract.__init__(self, **options) @@ -35,18 +39,10 @@ def __init__(self, options: NetworkProviderOptions = {}): Registry storage calls for all providers """ - # async def task(func, action, args1=[], args2=[]): - # item = func(*args1).action(*args2) - # sleep(5) - - # if item: - # return item - # return - async def get_all_providers(self) -> list: """Get all providers in Registry Contract. - returns a list of oracles once async is fulfilled + :returns a list of oracles once async is fulfilled """ await sleep(3) return self.contract.functions.getAllOracles().call() @@ -54,7 +50,10 @@ async def get_all_providers(self) -> list: 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 + :param index: + Index of the provider in the list of addresses + + :returns address of indexed provider once async is fulfilled """ await sleep(0.6) return self.contract.functions.getOracleAddress(index).call() @@ -64,19 +63,23 @@ async def get_provider_address_by_index(self, index: int) -> str: """ async def initiate_provider(self, public_key: str, title: str, - From: address, cb: TransactionCallback = None, - gas=const.DEFAULT_GAS) -> txid: + From: address, + gas=const.DEFAULT_GAS, + cb: Optional[TransactionCallback] = None + ) -> txid: """ 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 + :param public_key: a public identifier for this oracle + + :param title: describes what data this oracle provides + + :param from: + Ethereum address of the account that is + initializing this provider + + :param gas: Sets the gas limit for this transaction. """ @@ -95,9 +98,9 @@ async def initiate_provider(self, public_key: str, title: str, 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 + :param provider: The address of this provider - returns the public key number. + :returns the public key number. """ await sleep(0.8) return self.contract.functions.getProviderPublicKey(provider).call() @@ -105,9 +108,9 @@ async def get_provider_publickey(self, provider: address) -> int: async def get_provider_title(self, provider: address) -> str: """ Get a provider's title from the Registry contract. - address -- The address of this provider. + :param address: The address of this provider. - return a future that will eventually resolve into a title string + :return a future that will eventually resolve into a title string """ await sleep(1) title = Web3.toText(self.contract.functions.getProviderTitle( @@ -116,14 +119,15 @@ async def get_provider_title(self, provider: address) -> str: return title async def set_provider_title(self, From: address, title: str, - cb: TransactionCallback = None, - gas=const.DEFAULT_GAS) -> txid: + gas=const.DEFAULT_GAS, + cb: TransactionCallback = None) -> 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 + :param From: The address of this provider + + :param title: The new title of this provider + + :param cb: Callback for transactionHash event """ try: @@ -140,9 +144,10 @@ async def set_provider_title(self, From: address, title: str, 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. + :param provider: + Gets whether this provider has already been created. - returns a future that will eventually resolve a true/false value. + :returns a future that will eventually resolve a true/false value. """ await sleep(0.13) return self.contract.functions.isProviderInitiated(provider).call() @@ -157,31 +162,35 @@ async def set_provider_param(self, key: int, value: int, async def get_provider_param(self, provider: address, key: int) -> bytes: """ Get a parameter from a provider - provider -- The address of the provider - key -- The key you're getting + :param provider: The address of the provider + :param key: The key you're getting - returns a future that will be resolved with the value of the keys + :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() + 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 - provider -- The address of the provider + :param provider: The address of the provider - returns a future that will be resolved with all the keys + :returns a future that will be resolved with all the keys """ await sleep(0.89) - return await self.contract.functions.getAllProviderParams(provider).call() + return await\ + self.contract.functions.getAllProviderParams(provider).call() async def get_provider_endpoints(self, provider: address) -> List[str]: """ Get the endpoints of a given provider - provider -- The address of this provider + :param provider: The address of this provider - returns a Future that will be eventually resolved with - the list of endpoints of the 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( @@ -196,7 +205,7 @@ async def get_provider_endpoints(self, provider: address) -> List[str]: """ async def initiate_provider_curve(self, end_point, term, - From, gasPrice, + From, gas_price, cb: TransactionCallback = None, broker=const.NULL_ADDRESS, gas=const.DEFAULT_GAS) -> txid: @@ -207,21 +216,21 @@ async def initiate_provider_curve(self, end_point, term, tx_hash: txid = self.contract.functions.initiateProviderCurve( Web3.toBytes(text=end_point), term, broker).transact( - {"from": From, "gas": gas, "gasPrice": int(gasPrice)}) + {"from": From, "gas": gas, "gasPrice": gas_price}) if cb: cb(None, tx_hash) return tx_hash.hex() except ValueError as e: print(str(e)) - async def clear_endpoint(self, endpoint, From, gasPrice, + async def clear_endpoint(self, endpoint, From, gas_price, cb: TransactionCallback = None, gas=const.DEFAULT_GAS) -> txid: try: await sleep(1.6) tx_hash: txid = self.contract.functions.clearEndpoint( Web3.toBytes(text=endpoint)).transact( - {"from": From, "gas": gas, "gasPrice": gasPrice}) + {"from": From, "gas": gas, "gasPrice": gas_price}) if cb: cb(None, tx_hash) return tx_hash.hex() @@ -240,7 +249,7 @@ def encode_params(self, endpoint_params: list = [], ): hex_params =\ [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] + [Web3.toBytes(hexstr=hex_p) for hex_p in hex_params] params = [] from math import ceil @@ -257,9 +266,8 @@ def encode_params(self, endpoint_params: list = [], ): return params def decode_params(self, raw_params: List[str] = []): - bytes_params = [bytearray(Web3.toBytes(hexstr=el)) - - for el in raw_params] + bytes_params =\ + [Web3.toBytes(hexstr=el) for el in raw_params] params = [] i = 0 length = len(bytes_params) @@ -293,7 +301,7 @@ def decode_params(self, raw_params: List[str] = []): print(e) async def set_endpoint_params(self, endpoint: str, From: address, - gasPrice: int, + gas_price: int, cb: Optional[TransactionCallback] = None, endpoint_params: Optional[List[str]] = [], gas: Optional[int] = const.DEFAULT_GAS @@ -301,16 +309,21 @@ async def set_endpoint_params(self, endpoint: str, From: address, """ 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 endpoint: Data endpoint of the provider + + :param endpoint_params: + The parameters that this endpoint accepts as query arguments + + :param from: The address of the owner of this oracle + + :param 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 + :returns + Future(txid) Returns a Promise that will eventually + resolve into a transaction hash """ params = self.encode_params(endpoint_params) @@ -318,7 +331,7 @@ async def set_endpoint_params(self, endpoint: str, From: address, await sleep(0.53) tx_hash = self.contract.functions.setEndpointParams( Web3.toBytes(text=endpoint), params).transact( - {"from": From, "gas": gas}) + {"from": From, "gas": gas, "gasPrice": gas_price}) if cb: cb(None, tx_hash) return tx_hash.hex() diff --git a/tests/Registry/conftest.py b/tests/Registry/conftest.py index 42d8db6b..9529af8c 100644 --- a/tests/Registry/conftest.py +++ b/tests/Registry/conftest.py @@ -7,8 +7,8 @@ from sys import path path.insert(0, realpath(join(__file__, "../../../src/"))) -from Artifacts.src.index import Artifacts -from ZapToken.Curve.curve import Curve +from artifacts.src import Artifacts +from zap_token.curve import Curve w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) abi = Artifacts["REGISTRY"] @@ -52,7 +52,7 @@ def coor_funcs(coor): def _ZapRegistry(reg_contract): - """ WIP: Returns an object representation of ZapRegistry for testing. + """ Returns an object representation of ZapRegistry for testing. This ZapRegistry object has a mocked BaseContract used for its init phase. @@ -84,8 +84,8 @@ def __init__(self, artifact_name, artifact_dir=None, network_id=None, self.address = self.artifact["networks"][self.network_id]["address"] self.contract = reg_contract - mp.setattr("Registry.registry.BaseContract", MockBaseContract) - from Registry.registry import ZapRegistry + mp.setattr("registry.BaseContract", MockBaseContract) + from registry import ZapRegistry try: return(ZapRegistry({"network_id": "31337"})) diff --git a/tests/Registry/test_registry.py b/tests/Registry/test_registry.py index 83edc9e7..6e79bd78 100644 --- a/tests/Registry/test_registry.py +++ b/tests/Registry/test_registry.py @@ -10,7 +10,7 @@ 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 fixture, mark +from pytest import mark from web3 import Web3 @@ -18,8 +18,8 @@ from sys import path path.insert(0, realpath(join(__file__, "../../../src/"))) -from ZapToken.Curve.curve import Curve -from Artifacts.src.index import Artifacts +from zap_token.curve import Curve +from artifacts.src import Artifacts """ pytest section @@ -133,7 +133,7 @@ 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)} + "From": account, "gas_price": int(5e4)} curve = await instance.initiate_provider_curve(**opts) From 4c0c8db9e7c8f316ddcb8ff48926acc5f8d71479 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Thu, 1 Apr 2021 07:24:17 -0400 Subject: [PATCH 17/17] Bondage Port with Tests; Bondage ported from monorepo and pythia; Transactions have the option to parse in a callback that can interact with their respective events; Tests were written for transactions and calls and their valid outputs. --- src/bondage/__init__.py | 447 ++++++++++++++++++++++++++-------- tests/Bondage/conftest.py | 151 ++++++++++++ tests/Bondage/test_bondage.py | 194 +++++++++++++++ 3 files changed, 695 insertions(+), 97 deletions(-) create mode 100644 tests/Bondage/conftest.py create mode 100644 tests/Bondage/test_bondage.py diff --git a/src/bondage/__init__.py b/src/bondage/__init__.py index 00573184..9fbe818f 100644 --- a/src/bondage/__init__.py +++ b/src/bondage/__init__.py @@ -1,104 +1,357 @@ -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) +from asyncio import sleep -class ZapBondage(BaseConstract): +from web3 import Web3 - def __init__(self, NetworkProviderOptions): - super().__init__(self, NetworkProviderOptions or {}, { artifactName: 'Bondage'}) +from base_contract import BaseContract +from registry import ZapRegistry +from zaptypes import ( + BondFilter, txid, NetworkProviderOptions, + TransactionCallback, const, address +) + + +class ZapBondage(BaseContract): + """ Provides an interface to the Bondage contract. + Enables bonding and unbonding to Oracles. + """ + + def __init__(self, options: NetworkProviderOptions): + options["artifact_name"] = "BONDAGE" + BaseContract.__init__(self, **options or {}) + + async def bond(self, provider: address, endpoint: str, + dots: int, From: address, gas_price: int, + gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None) -> txid: + """ Bonds a number of dots from a subscriber to a provider's endpoint. + + Note: this requires that at least zapNum has been approved from + the subscriber to be transferred by the Bondage contract. + + :param provider: Provider's address + + :param dots: number of dots to bond to this provider + + :param From: Subscriber's owner (0 broker) or broker's address + + :param gas: Sets the gas limit for this transaction + + :param cb: Callback for transactionHash event + + :returns + Coroutine that eventually resolves into a transaction hash. + """ + assert (dots and dots > 0),\ + "Dots to bond must be greater than 0." + + payload = {"oracleAddress": provider, + "endpoint": Web3.toBytes(text=endpoint)} + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + + broker =\ + self.contract.functions.getEndpointBroker(**payload).call() - 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 From != broker: + raise Exception(f"Broker address {broker} needs to call delegate bonding") + + payload["numDots"] = dots + + try: + await sleep(0) + tx_hash = self.contract.functions.bond( + **payload).transact(tx_meta) + if cb: + receipt = tx_hash + logs = self.contract.events.Bound().processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + return tx_hash.hex() + except Exception as e: + print(e) + + async def delegate_bond(self, provider: address, subscriber: address, + endpoint: str, dots: int, From: address, + gas_price: int, gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None) -> txid: + """ Bonds a given number of dots from an account to a subscriber. + + This would be used to bond to a provider + on behalf of another account, such as a smart contract. + + :param provider: Provider's address + + :param endpoint: Data endpoint of the provider + + :param dots: Number of dots to bond to this provider + + :param subscriber: Address of the intended holder of the dots + + :param From: Address of the data subscriber + + :param gas: Sets the gas limit for this transaction + + :param cb: Callback for transactionHash event + + :returns + Coroutine that eventually resolves into a transaction hash. + """ + + assert (dots and dots > 0),\ + "Dots to bond must be greater than 0." + + payload = {"oracleAddress": provider, + "endpoint": Web3.toBytes(text=endpoint)} + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + + broker =\ + self.contract.functions.getEndpointBroker(**payload).call() + 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 From != broker: + raise Exception(f"Broker address {broker} needs to call delegate bonding") + + payload.update({"numDots": dots, "holderAddress": subscriber}) + + try: + await sleep(0) + tx_hash = self.contract.functions.delegateBond( + **payload).transact(tx_meta) + + if cb: + receipt = tx_hash + logs = self.contract.events.Bound().processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + + return tx_hash.hex() + except Exception as e: + print(e) + + async def unbond(self, provider: address, + endpoint: str, dots: int, From: address, + gas_price: int, gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None) -> txid: + """ Unbonds a given number of dots. + + It unbonds from a provider's endpoint and transfers the + appropriate amount of Zap to the subscriber. + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :param dots: The number of dots to unbond from the contract + + :param From : Address of the data subscriber + + :param gas: Sets the gas limit for this transaction (optional) + + :param cb: Callback for transactionHash event + + :returns + Coroutine that eventually resolves into a transaction hash. + """ + + assert (dots and dots > 0),\ + "Dots to unbond must be greater than 0." + + payload = {"oracleAddress": provider, "endpoint": Web3.toBytes(text=endpoint)} + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + + broker =\ + self.contract.functions.getEndpointBroker(**payload).call() + 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) + if From != broker: + raise Exception(f"Broker address {broker} needs to call delegate bonding") + + payload["numDots"] = dots + + try: + await sleep(0) + tx_hash = self.contract.functions.unbond( + **payload).transact(tx_meta) + + if cb: + receipt = tx_hash + logs = self.contract.events.Bound().processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + + return tx_hash + except Exception as e: + print(e) + + """ Getters + """ + + async def get_bound_dots(self, subscriber: address, provider: address, + endpoint: str) -> int: + """ Gets the number of dots that are bounded to a provider's endpoint + for the current subscriber. + + :param subscriber: Address of the data subscriber + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :returns + A Coroutine that resolves to the number of + bound dots to this provider's endpoint + """ + await sleep(0) + return self.contract.functions.getBoundDots( + subscriber, provider, Web3.toBytes(text=endpoint)).call() + + async def calc_zap_for_dots(self, provider: address, + endpoint: str, dots: int): + """ Calculates the amount of Zap required to bond + a given number of dots to a provider's endpoint. + + :param provider: Address of the data provider + + :param endpoint: Endpoint to calculate zap + + :param dots: Number of dots to calculate the price (in Zap) for + + :returns + A Coroutine that resolves to the price (in Zap) + for the given number of dots + """ + await sleep(0) + return self.contract.functions.calcZapForDots( + provider, Web3.toBytes(text=endpoint), dots).call() + + async def current_cost_of_dot(self, provider: address, + endpoint: str, dots: int) -> int: + """ Calculates the amount of Zap required to bond + a given number of dots to a provider's endpoint. + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :param dots: dots that subscriber want to use + + :returns + A Coroutine that resolves into the price (in Zap wei) + for next x dots to bond + """ + await sleep(0) + return self.contract.functions.currentCostOfDot( + provider, Web3.toBytes(text=endpoint), dots).call() + + async def get_dots_limits(self, provider: address, endpoint: str) -> int: + """ Get Maximum dots that can be bound for an endpoint of a provider + + :param provider: Provider's address + + :param endpoint: Provider's endpoint to get dots limit + + :returns + A Coroutine that resolves into the number + of maximum dots that can be bound + + """ + await sleep(0) + return self.contract.functions.dotLimit( + provider, Web3.toBytes(text=endpoint)).call() + + async def get_dots_issued(self, provider: address, endpoint: str) -> int: + """ Gets the total number of dots that have been issued + by a provider's endpoint. + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :returns A Coroutine that resolves into the number of dots issued + + """ + await sleep(0) + return self.contract.functions.getDotsIssued( + provider, Web3.toBytes(text=endpoint)).call() + + async def get_broker_address(self, provider: address, endpoint: str): + """ Get Broker address for this provider's endpoint + return NULL_ADDRESS if there is none + + :param provider: Provider's Address + + :param endpoint: Provider's endpoint to get Broker's address + + :returns + A Coroutine that resolves into a broker's + address for this endpoint, null address if none + + """ + sleep(0) + return await\ + self.contract.functions.getEndpointBroker( + provider, Web3.toBytes(text=endpoint)).call() + + async def get_zap_bound(self, provider: address, endpoint: str) -> int: + """ Gets the total amount of Zap that has been bonded + to a provider's endpoint. + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :returns A Coroutine that resolves into the amount of Zaps (wei) + that are bound to this endpoint + + """ + await sleep(0) + return self.contract.functions.getZapBound( + provider, Web3.toBytes(text=endpoint)).call() + + async def get_num_escrow(self, provider: address, subscriber: address, + endpoint: str) -> int: + """ Get Number of dots escrow + + :param provider + + :param endpoint + + :param subscriber + + :returns A Coroutine that resolve into the number of escrow dots + + """ + await sleep(0) + return self.contract.functions.getNumEscrow( + subscriber, provider, Web3.toBytes(text=endpoint)).call() + + """ Events + """ + + def listen(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.allEvents( + filters, {"fromBlock": 0, "toBlock": "latest"}, cb) + + def listen_bound(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Bound(filters, {"toBlock": "latest"}, cb) + + def listen_unbound(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Unbond(filters, {"toBlock": "latest"}, cb) + + def listen_escrow(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Escrowed(filters, {"toBlock": "latest"}, cb) + + def listen_released(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Released(filters, {"toBlock": "latest"}, cb) + def listen_returned(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Returned(filters, {"toBlock": "latest"}, cb) diff --git a/tests/Bondage/conftest.py b/tests/Bondage/conftest.py new file mode 100644 index 00000000..aa7f6253 --- /dev/null +++ b/tests/Bondage/conftest.py @@ -0,0 +1,151 @@ +from pytest import MonkeyPatch, fixture + +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.curve import Curve + +_w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) +_abi = {"bondage": Artifacts["BONDAGE"], + "zap_token": Artifacts["ZAP_TOKEN"]} +coor_artifact = Artifacts["ZAPCOORDINATOR"] + +""" Arrange/SetUp Section +""" + + +@fixture(scope="module") +def w3(): + return _w3 + + +@fixture(scope="module") +def address(w3): + return {name: Web3.toChecksumAddress( + abi["networks"]["31337"]["address"]) + for name, abi in _abi.items()} + + +@fixture(scope="module") +def bond_contract(w3, address): + _bond_contract = w3.eth.contract( + address=address["bondage"], + abi=_abi["bondage"]["abi"]) + + return _bond_contract + + +@fixture(scope="class") +def zt_contract(w3, address): + _zt_contract = w3.eth.contract( + address=address["zap_token"], + abi=_abi["zap_token"]["abi"]) + + return _zt_contract + + +@fixture(scope="module") +def functions(bond_contract): + return bond_contract.functions + + +@fixture(scope="module") +def accounts(w3): + return w3.eth.accounts + + +@fixture(scope="module") +def owner(accounts): + return accounts[0] + + +@fixture(scope="module") +def subscriber(accounts): + return accounts[1] + + +@fixture(scope="module") +def oracle(accounts): + return accounts[8] + + +@fixture(scope="module") +def broker(accounts): + return accounts[3] + + +@fixture(scope="module") +def escrower(accounts): + return accounts[4] + + +def _ZapBondage(bond_contract): + + 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 = bond_contract + + mp.setattr("bondage.BaseContract", MockBaseContract) + from bondage import ZapBondage + + try: + return(ZapBondage({"network_id": "31337"})) + except Exception as e: + raise e + + +@fixture(scope="module") +def Zap_Bondage(): + + zap_bond_obj = _ZapBondage + yield zap_bond_obj + del zap_bond_obj + + +@fixture(scope="module") +def provider(oracle): + return {"pubkey": 108, + "title": '0x4269616e6361', + "address": oracle, + "endpoint_params": ['param1', 'param2'], + "endpoint": 'Fibonacci', + "query": 'btcPrice', + "curve": Curve([2, 5000, 2000, 1000, + 2, 0, 3000, 10000]), + "broker": '0x0000000000000000000000000000000000000000' + } + + +@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_Bondage, bond_contract): + _instance = Zap_Bondage(bond_contract) + return _instance diff --git a/tests/Bondage/test_bondage.py b/tests/Bondage/test_bondage.py new file mode 100644 index 00000000..c71ce3ae --- /dev/null +++ b/tests/Bondage/test_bondage.py @@ -0,0 +1,194 @@ +from pytest import mark, fixture + +from web3 import Web3 + +from os.path import join, realpath +from sys import path +path.insert(0, realpath(join(__file__, "../../../src/"))) + +from zap_token.curve import Curve +from artifacts.src import Artifacts +from zaptypes import const + + +class TestBondage: + """docstring for TestBondage""" + + global tx_payload, tx_del_payload + tx_payload = {} + tx_del_payload = {} + + global required + required = 0 + + def test_init(self, instance, bond_contract, subscriber, + oracle, provider, w3): + + global tx_payload + + assert instance + assert instance.contract.address == bond_contract.address + + tx_payload = {"provider": oracle, "endpoint": provider["endpoint"], + "From": subscriber, "gas_price": w3.eth.gas_price} + + @fixture(scope="class", autouse=True) + def prepare_tokens(self, instance, zt_contract, + subscriber, owner, broker, w3): + bondage_owner = instance.contract.address + zt_contract.functions.allocate( + owner, 1500000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + zt_contract.functions.allocate( + subscriber, 50000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + zt_contract.functions.allocate( + broker, 50000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + zt_contract.functions.approve( + bondage_owner, 1000000000000000000000000000000).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + zt_contract.functions.approve( + bondage_owner, 1000000000000000000000000000000).transact( + {"from": broker, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + @mark.anyio + async def test_bond(self, instance): + + print("\n\nTesting bond") + tx_hash = await instance.bond(dots=999, **tx_payload) + + assert isinstance(tx_hash, str) + print("✅ Passed\n") + + print("\nTesting get_bound_dots") + bonded = await instance.get_bound_dots( + tx_payload["From"], tx_payload["provider"], + tx_payload["endpoint"]) + print("✅ Passed\n") + + print("\nConfirming dots were bonded") + assert isinstance(bonded, int) + assert bonded == 999 + + @mark.anyio + async def test_delegate_bond(self, instance, escrower): + + print("\n\nTesting delegate_bond") + tx_hash = await instance.delegate_bond( + subscriber=escrower, dots=999, **tx_payload) + + assert isinstance(tx_hash, str) + print("✅ Passed\n") + + print("\nConfirming dots were bonded") + bonded = await instance.get_bound_dots( + escrower, tx_payload["provider"], + tx_payload["endpoint"]) + + assert isinstance(bonded, int) + assert bonded == 999 + + @mark.anyio + async def test_calc_zap_for_dots(self, instance): + global required + required = await\ + instance.calc_zap_for_dots( + tx_payload["provider"], + tx_payload["endpoint"], + 5) + + assert isinstance(required, int) + assert required > 0 + + @mark.anyio + async def test_current_cost_of_dot(self, instance): + cost = await\ + instance.current_cost_of_dot( + tx_payload["provider"], + tx_payload["endpoint"], + 5) + + assert isinstance(cost, int) + assert cost > 0 + + @mark.anyio + async def test_get_dots_limits(self, instance): + limit = await\ + instance.get_dots_limits( + tx_payload["provider"], + tx_payload["endpoint"]) + + assert isinstance(limit, int) + assert limit > 0 + + @mark.anyio + async def test_get_dots_issued(self, instance, escrower): + issued = await\ + instance.get_dots_issued( + tx_payload["provider"], + tx_payload["endpoint"]) + + bonded = await instance.get_bound_dots( + tx_payload["From"], tx_payload["provider"], + tx_payload["endpoint"]) + + bonded_del = await instance.get_bound_dots( + escrower, tx_payload["provider"], + tx_payload["endpoint"]) + + print("\n\nTesting get_bound_dots") + assert isinstance(issued, int) + assert issued > 0 + print("✅ Passed\n") + print("\nConfirming dots issued == total bonded") + assert issued == bonded + bonded_del + print("Dots issued: ", issued) + + @mark.anyio + async def test_get_zap_bound(self, instance): + zap_bonded = await\ + instance.get_zap_bound( + tx_payload["provider"], + tx_payload["endpoint"]) + + assert isinstance(zap_bonded, int) + print("Num of Zap bonded", zap_bonded) + assert zap_bonded > 0 + + @mark.anyio + async def test_get_num_escrow(self, instance, escrower): + + escrowed = await\ + instance.get_num_escrow( + tx_payload["provider"], + escrower, + tx_payload["endpoint"]) + + assert isinstance(escrowed, int) + print("Num Escrowed: ", escrowed) + + bonded = await instance.get_bound_dots( + tx_payload["From"], tx_payload["provider"], + tx_payload["endpoint"]) + + bonded_del = await instance.get_bound_dots( + escrower, tx_payload["provider"], + tx_payload["endpoint"]) + + print("# of bonded dots: ", bonded) + print("# of bonded dots delegated: ", bonded_del) + + print("\nTesting unbond") + await instance.unbond(dots=bonded, **tx_payload) + tx_payload["From"] = escrower + await instance.unbond(dots=bonded_del, **tx_payload)