From 5740b168b941aa914731b9322ba3dbbeb4ce532d Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Mon, 1 Mar 2021 11:21:27 -0400 Subject: [PATCH 01/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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 dd28159dffbd296cc0d3ece1f266e1608ca9a8e9 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Thu, 1 Apr 2021 11:44:41 -0400 Subject: [PATCH 17/21] Fixed event listening for transactionHash; provided w3 fixture for tests; getting gas_price via web3.eth --- src/registry/__init__.py | 37 +++++++++++++++++++++++---------- tests/Registry/conftest.py | 15 ++++++++----- tests/Registry/test_registry.py | 16 +++++++------- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/registry/__init__.py b/src/registry/__init__.py index a424bb2e..d3ee66c2 100644 --- a/src/registry/__init__.py +++ b/src/registry/__init__.py @@ -64,6 +64,7 @@ async def get_provider_address_by_index(self, index: int) -> str: async def initiate_provider(self, public_key: str, title: str, From: address, + gas_price, gas=const.DEFAULT_GAS, cb: Optional[TransactionCallback] = None ) -> txid: @@ -83,13 +84,20 @@ async def initiate_provider(self, public_key: str, title: str, """ + payload = {"publicKey": public_key, "title": Web3.toBytes(text=title)} + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + try: await sleep(3) tx_hash: txid = self.contract.functions.initiateProvider( - public_key, Web3.toBytes(text=title)).transact( - {"from": From, "gas": gas}) + **payload).transact(tx_meta) if cb: - cb(None, tx_hash) + receipt = tx_hash + logs = self.contract.events.NewProvider( + ).processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) return tx_hash.hex() except ValueError as e: @@ -119,6 +127,7 @@ async def get_provider_title(self, provider: address) -> str: return title async def set_provider_title(self, From: address, title: str, + gas_price, gas=const.DEFAULT_GAS, cb: TransactionCallback = None) -> txid: """ Set the new provider's title @@ -134,7 +143,7 @@ async def set_provider_title(self, From: address, title: str, await sleep(1.4) tx_hash: txid = self.contract.functions.setProviderTitle( Web3.toBytes(text=title)).transact( - {"from": From, "gas": gas}) + {"from": From, "gas": gas, "gasPrice": gas_price}) cb(None, tx_hash) return tx_hash.hex() @@ -204,21 +213,27 @@ async def get_provider_endpoints(self, provider: address) -> List[str]: Provider's specific endpoint calls """ - async def initiate_provider_curve(self, end_point, term, + async def initiate_provider_curve(self, endpoint, term, From, gas_price, cb: TransactionCallback = None, broker=const.NULL_ADDRESS, gas=const.DEFAULT_GAS) -> txid: """""" - await sleep(0.247) + payload = {"endpoint": Web3.toBytes(text=endpoint), + "curve": term, "broker": broker} + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} try: + await sleep(0.247) tx_hash: txid = self.contract.functions.initiateProviderCurve( - Web3.toBytes(text=end_point), - term, broker).transact( - {"from": From, "gas": gas, "gasPrice": gas_price}) + **payload).transact(tx_meta) if cb: - cb(None, tx_hash) + receipt = tx_hash + logs = self.contract.events.NewCurve( + ).processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) return tx_hash.hex() except ValueError as e: print(str(e)) @@ -359,7 +374,7 @@ async def is_endpoint_set(self, provider: address, endpoint: str): async def listen(self, callback: Callable[..., None]): sleep(3) - self.contract.events.allEvents(callback) + self.contract.events._events(callback) async def listen_new_provider(self, callback: TransactionCallback, filters: Filter = {}): diff --git a/tests/Registry/conftest.py b/tests/Registry/conftest.py index 9529af8c..f050c1aa 100644 --- a/tests/Registry/conftest.py +++ b/tests/Registry/conftest.py @@ -10,7 +10,7 @@ from artifacts.src import Artifacts from zap_token.curve import Curve -w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) +_w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) abi = Artifacts["REGISTRY"] coor_artifact = Artifacts["ZAPCOORDINATOR"] _address = abi["networks"]["31337"]["address"] @@ -21,7 +21,12 @@ @fixture(scope="module") -def reg_contract(): +def w3(): + return _w3 + + +@fixture(scope="module") +def reg_contract(w3): _reg_contract = w3.eth.contract(address=_address, abi=abi["abi"]) return _reg_contract @@ -29,7 +34,7 @@ def reg_contract(): @fixture(scope="module") def coor(): - _coor = w3.eth.contract( + _coor = _w3.eth.contract( abi=coor_artifact['abi'], address=coor_artifact['networks']["31337"]['address']) return _coor @@ -75,7 +80,7 @@ def __init__(self, artifact_name, artifact_dir=None, network_id=None, self.name = artifact_name self.artifact = Artifacts[artifact_name] coor_artifact = Artifacts["ZAPCOORDINATOR"] - self.provider = web3 or w3 + self.provider = web3 or _w3 self.network_id = network_id or "1" self.coordinator = self.provider.eth.contract( abi=coor_artifact['abi'], @@ -123,7 +128,7 @@ def oracle(functions): @fixture(scope="module") def account(): - return w3.eth.accounts[11] + return _w3.eth.accounts[11] @fixture(scope="module") diff --git a/tests/Registry/test_registry.py b/tests/Registry/test_registry.py index 6e79bd78..d4f7003f 100644 --- a/tests/Registry/test_registry.py +++ b/tests/Registry/test_registry.py @@ -56,9 +56,11 @@ 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, title): + async def test_initiate_provider(self, instance, pubkey, + account, title, w3): opt = {"public_key": pubkey, "title": title, - "From": account, "gas": 4 * 10**5} + "From": account, "gas": 4 * 10**5, + "gas_price": w3.eth.gas_price} tx = await instance.initiate_provider(**opt) assert isinstance(tx, str) @@ -87,10 +89,10 @@ async def test_is_provider_initiated(self, instance, account): assert isinstance(isInit, bool) @mark.anyio - async def test_set_provider_title(self, instance, account): + async def test_set_provider_title(self, instance, account, w3): title_test = "REGISTRY" - tx = await instance.set_provider_title(account, title_test) + tx = await instance.set_provider_title(account, title_test, w3.eth.gas_price) assert tx print(tx) @@ -130,10 +132,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, - endpoint, curve_values): + endpoint, curve_values, w3): term = curve_values - opts = {"end_point": endpoint, "term": term, - "From": account, "gas_price": int(5e4)} + opts = {"endpoint": endpoint, "term": term, + "From": account, "gas_price": w3.eth.gas_price} curve = await instance.initiate_provider_curve(**opts) From a6a66f72062db602f974746da6167eedb0b88364 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Fri, 2 Apr 2021 19:20:59 -0400 Subject: [PATCH 18/21] added getFactories to TokenDotFactory ABI --- src/Artifacts/contracts/TokenDotFactory.json | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Artifacts/contracts/TokenDotFactory.json b/src/Artifacts/contracts/TokenDotFactory.json index f3cf2b6e..df4dd5b2 100644 --- a/src/Artifacts/contracts/TokenDotFactory.json +++ b/src/Artifacts/contracts/TokenDotFactory.json @@ -260,6 +260,28 @@ "payable": false, "stateMutability": "pure", "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getFactories", + "outputs": [ + { + "name": "", + "type": "address[]" + }, + { + "name": "", + "type": "uint256[]" + }, + { + "name": "", + "type": "bytes32[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" } ], "networks": { @@ -274,6 +296,12 @@ "links": {}, "address": "0xf108237201E6EE3906f19390C699dA4d1495040F", "transactionHash": "0x52a4cdc19fdb99c432317d14b4b27254c430998fc8baa4c9a56bcc6e9915f1eb" + }, + "31337": { + "events": {}, + "links": {}, + "address": "0x809d550fca64d94bd9f66e60752a544199cfac3d", + "transactionHash": "0xcf5a0d23a80b4109afdc0bce7e496c6bd7b8491c683e9e5ebadd4e173da64e6c" } } } \ No newline at end of file From d82661e03b016065a9a3f60ae2b3d45678a71e51 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Tue, 6 Apr 2021 16:02:51 -0400 Subject: [PATCH 19/21] ZapToken port with Tests --- src/arbiter/__init__.py | 6 + src/artifacts/__init__.py | 25 +++ .../contracts/Arbiter.json | 2 +- .../contracts/Bondage.json | 0 .../contracts/Client1.json | 0 .../contracts/Client2.json | 0 .../contracts/Client3.json | 0 .../contracts/Client4.json | 0 .../contracts/ClientBytes32Array.json | 0 .../contracts/ClientIntArray.json | 0 .../contracts/CurrentCost.json | 0 .../contracts/Dispatch.json | 0 .../contracts/Piecewise.json | 0 .../contracts/Registry.json | 2 +- .../contracts/RegistryInterface.json | 0 .../contracts/TokenDotFactory.json | 4 +- .../contracts/TokenFactory.json | 0 .../contracts/ZapCoordinator.json | 0 .../contracts/ZapCoordinatorInterface.json | 0 .../contracts/ZapToken.json | 2 +- src/{Artifacts => artifacts}/src/__init__.py | 0 .../src/tests/__init__.py | 0 .../src/tests/test_artifacts.py | 0 src/dispatch/__init__.py | 6 + src/registry/__init__.py | 2 - src/zap_token/__init__.py | 206 ++++++++++++++---- tests/ZapToken/conftest.py | 144 ++++++++++++ tests/ZapToken/test_zaptoken.py | 133 +++++++++++ 28 files changed, 487 insertions(+), 45 deletions(-) create mode 100644 src/arbiter/__init__.py create mode 100644 src/artifacts/__init__.py rename src/{Artifacts => artifacts}/contracts/Arbiter.json (99%) rename src/{Artifacts => artifacts}/contracts/Bondage.json (100%) rename src/{Artifacts => artifacts}/contracts/Client1.json (100%) rename src/{Artifacts => artifacts}/contracts/Client2.json (100%) rename src/{Artifacts => artifacts}/contracts/Client3.json (100%) rename src/{Artifacts => artifacts}/contracts/Client4.json (100%) rename src/{Artifacts => artifacts}/contracts/ClientBytes32Array.json (100%) rename src/{Artifacts => artifacts}/contracts/ClientIntArray.json (100%) rename src/{Artifacts => artifacts}/contracts/CurrentCost.json (100%) rename src/{Artifacts => artifacts}/contracts/Dispatch.json (100%) rename src/{Artifacts => artifacts}/contracts/Piecewise.json (100%) rename src/{Artifacts => artifacts}/contracts/Registry.json (99%) rename src/{Artifacts => artifacts}/contracts/RegistryInterface.json (100%) rename src/{Artifacts => artifacts}/contracts/TokenDotFactory.json (98%) rename src/{Artifacts => artifacts}/contracts/TokenFactory.json (100%) rename src/{Artifacts => artifacts}/contracts/ZapCoordinator.json (100%) rename src/{Artifacts => artifacts}/contracts/ZapCoordinatorInterface.json (100%) rename src/{Artifacts => artifacts}/contracts/ZapToken.json (99%) rename src/{Artifacts => artifacts}/src/__init__.py (100%) rename src/{Artifacts => artifacts}/src/tests/__init__.py (100%) rename src/{Artifacts => artifacts}/src/tests/test_artifacts.py (100%) create mode 100644 src/dispatch/__init__.py create mode 100644 tests/ZapToken/conftest.py create mode 100644 tests/ZapToken/test_zaptoken.py diff --git a/src/arbiter/__init__.py b/src/arbiter/__init__.py new file mode 100644 index 00000000..873b4f69 --- /dev/null +++ b/src/arbiter/__init__.py @@ -0,0 +1,6 @@ +class ZapArbiter(object): + """docstring for ZapArbiter""" + + def __init__(self, arg): + super(ZapArbiter, self).__init__() + self.arg = arg diff --git a/src/artifacts/__init__.py b/src/artifacts/__init__.py new file mode 100644 index 00000000..da3a4e1b --- /dev/null +++ b/src/artifacts/__init__.py @@ -0,0 +1,25 @@ +import os +import json + + +def load_json(name: str) -> dict: + path = f"{os.path.dirname(os.path.dirname(__file__))}/artifacts/contracts/" + with open(os.path.abspath(path + f"{name}.json")) as f: + abi_file = json.load(f) + return abi_file + + +Artifacts = { + 'ARBITER': load_json('Arbiter'), + 'BONDAGE': load_json('Bondage'), + 'DISPATCH': load_json('Dispatch'), + 'REGISTRY': load_json('Registry'), + 'CurrentCost': load_json('CurrentCost'), + 'ZAP_TOKEN': load_json('ZapToken'), + 'Client1': load_json('Client1'), + 'Client2': load_json('Client2'), + 'Client3': load_json('Client3'), + 'Client4': load_json('Client4'), + 'ZAPCOORDINATOR': load_json('ZapCoordinator'), + 'TOKENDOTFACTORY': load_json('TokenDotFactory'), +} diff --git a/src/Artifacts/contracts/Arbiter.json b/src/artifacts/contracts/Arbiter.json similarity index 99% rename from src/Artifacts/contracts/Arbiter.json rename to src/artifacts/contracts/Arbiter.json index b70befb1..561d738d 100644 --- a/src/Artifacts/contracts/Arbiter.json +++ b/src/artifacts/contracts/Arbiter.json @@ -419,7 +419,7 @@ "transactionHash": "0xd891f92a9be7d2cb45ff46bbdfdf1cf20cc9957a7f3137905e105abf24e01bc1" }, "31337": { - "address": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + "address": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0" } } } \ No newline at end of file diff --git a/src/Artifacts/contracts/Bondage.json b/src/artifacts/contracts/Bondage.json similarity index 100% rename from src/Artifacts/contracts/Bondage.json rename to src/artifacts/contracts/Bondage.json diff --git a/src/Artifacts/contracts/Client1.json b/src/artifacts/contracts/Client1.json similarity index 100% rename from src/Artifacts/contracts/Client1.json rename to src/artifacts/contracts/Client1.json diff --git a/src/Artifacts/contracts/Client2.json b/src/artifacts/contracts/Client2.json similarity index 100% rename from src/Artifacts/contracts/Client2.json rename to src/artifacts/contracts/Client2.json diff --git a/src/Artifacts/contracts/Client3.json b/src/artifacts/contracts/Client3.json similarity index 100% rename from src/Artifacts/contracts/Client3.json rename to src/artifacts/contracts/Client3.json diff --git a/src/Artifacts/contracts/Client4.json b/src/artifacts/contracts/Client4.json similarity index 100% rename from src/Artifacts/contracts/Client4.json rename to src/artifacts/contracts/Client4.json diff --git a/src/Artifacts/contracts/ClientBytes32Array.json b/src/artifacts/contracts/ClientBytes32Array.json similarity index 100% rename from src/Artifacts/contracts/ClientBytes32Array.json rename to src/artifacts/contracts/ClientBytes32Array.json diff --git a/src/Artifacts/contracts/ClientIntArray.json b/src/artifacts/contracts/ClientIntArray.json similarity index 100% rename from src/Artifacts/contracts/ClientIntArray.json rename to src/artifacts/contracts/ClientIntArray.json diff --git a/src/Artifacts/contracts/CurrentCost.json b/src/artifacts/contracts/CurrentCost.json similarity index 100% rename from src/Artifacts/contracts/CurrentCost.json rename to src/artifacts/contracts/CurrentCost.json diff --git a/src/Artifacts/contracts/Dispatch.json b/src/artifacts/contracts/Dispatch.json similarity index 100% rename from src/Artifacts/contracts/Dispatch.json rename to src/artifacts/contracts/Dispatch.json diff --git a/src/Artifacts/contracts/Piecewise.json b/src/artifacts/contracts/Piecewise.json similarity index 100% rename from src/Artifacts/contracts/Piecewise.json rename to src/artifacts/contracts/Piecewise.json diff --git a/src/Artifacts/contracts/Registry.json b/src/artifacts/contracts/Registry.json similarity index 99% rename from src/Artifacts/contracts/Registry.json rename to src/artifacts/contracts/Registry.json index 140ca7be..342ef9f0 100644 --- a/src/Artifacts/contracts/Registry.json +++ b/src/artifacts/contracts/Registry.json @@ -561,7 +561,7 @@ "42": { "events": {}, "links": {}, - "address": "0x513846a568407ebd16bc29d238c364702963377d", + "address": "0x26BC483E8f4E868B031b29973232c188B941a3D8", "transactionHash": "0x9e913c416007746fb8967fb52558117ec97fd174b9f5368af08c7b951ccbb54e" }, "31337": { diff --git a/src/Artifacts/contracts/RegistryInterface.json b/src/artifacts/contracts/RegistryInterface.json similarity index 100% rename from src/Artifacts/contracts/RegistryInterface.json rename to src/artifacts/contracts/RegistryInterface.json diff --git a/src/Artifacts/contracts/TokenDotFactory.json b/src/artifacts/contracts/TokenDotFactory.json similarity index 98% rename from src/Artifacts/contracts/TokenDotFactory.json rename to src/artifacts/contracts/TokenDotFactory.json index df4dd5b2..a881ccd7 100644 --- a/src/Artifacts/contracts/TokenDotFactory.json +++ b/src/artifacts/contracts/TokenDotFactory.json @@ -4,7 +4,7 @@ "constant": true, "inputs": [ { - "name": "", + "name": "endpoint", "type": "bytes32" } ], @@ -294,7 +294,7 @@ "42": { "events": {}, "links": {}, - "address": "0xf108237201E6EE3906f19390C699dA4d1495040F", + "address": "0xb0Bc8151Bcd5824359298587395655923F4c6AcB", "transactionHash": "0x52a4cdc19fdb99c432317d14b4b27254c430998fc8baa4c9a56bcc6e9915f1eb" }, "31337": { diff --git a/src/Artifacts/contracts/TokenFactory.json b/src/artifacts/contracts/TokenFactory.json similarity index 100% rename from src/Artifacts/contracts/TokenFactory.json rename to src/artifacts/contracts/TokenFactory.json diff --git a/src/Artifacts/contracts/ZapCoordinator.json b/src/artifacts/contracts/ZapCoordinator.json similarity index 100% rename from src/Artifacts/contracts/ZapCoordinator.json rename to src/artifacts/contracts/ZapCoordinator.json diff --git a/src/Artifacts/contracts/ZapCoordinatorInterface.json b/src/artifacts/contracts/ZapCoordinatorInterface.json similarity index 100% rename from src/Artifacts/contracts/ZapCoordinatorInterface.json rename to src/artifacts/contracts/ZapCoordinatorInterface.json diff --git a/src/Artifacts/contracts/ZapToken.json b/src/artifacts/contracts/ZapToken.json similarity index 99% rename from src/Artifacts/contracts/ZapToken.json rename to src/artifacts/contracts/ZapToken.json index 8e285a3b..b81d8ffb 100644 --- a/src/Artifacts/contracts/ZapToken.json +++ b/src/artifacts/contracts/ZapToken.json @@ -408,7 +408,7 @@ "42": { "events": {}, "links": {}, - "address": "0xbb05077924d88d6d76b1d10b058ff7e31a658f07", + "address": "0x0331048143015c0784D0F9c723E709314aB87460", "transactionHash": "0x0323e79e6c0c01b5c7c2ced1d7bf1f8df695a02ddecefde5cb6fe187a88eb7d0" }, "4447": { diff --git a/src/Artifacts/src/__init__.py b/src/artifacts/src/__init__.py similarity index 100% rename from src/Artifacts/src/__init__.py rename to src/artifacts/src/__init__.py diff --git a/src/Artifacts/src/tests/__init__.py b/src/artifacts/src/tests/__init__.py similarity index 100% rename from src/Artifacts/src/tests/__init__.py rename to src/artifacts/src/tests/__init__.py diff --git a/src/Artifacts/src/tests/test_artifacts.py b/src/artifacts/src/tests/test_artifacts.py similarity index 100% rename from src/Artifacts/src/tests/test_artifacts.py rename to src/artifacts/src/tests/test_artifacts.py diff --git a/src/dispatch/__init__.py b/src/dispatch/__init__.py new file mode 100644 index 00000000..c8549fe9 --- /dev/null +++ b/src/dispatch/__init__.py @@ -0,0 +1,6 @@ +class ZapDispatch(object): + """docstring for ZapDispatch""" + + def __init__(self, arg): + super(ZapDispatch, self).__init__() + self.arg = arg diff --git a/src/registry/__init__.py b/src/registry/__init__.py index d3ee66c2..64a2c27c 100644 --- a/src/registry/__init__.py +++ b/src/registry/__init__.py @@ -16,8 +16,6 @@ class ZapRegistry(BaseContract): NetworkProviderOptions: Dictionary object containing options for BaseContract init - NetworkProviderOptions has the following keyword arguments: - :param arifactsDir: Directory where contract ABIs are located :param networkId: diff --git a/src/zap_token/__init__.py b/src/zap_token/__init__.py index abd48b75..3cb79c1c 100644 --- a/src/zap_token/__init__.py +++ b/src/zap_token/__init__.py @@ -1,43 +1,173 @@ +from asyncio import sleep + from base_contract import BaseContract -from base_contract import utils -# from .curve import Curve from zaptypes import ( - TransferType, address, txid, NetworkProviderOptions, TransactionCallback, NumType) -# from web3._utils. import (toHex) + address, txid, NetworkProviderOptions, + TransactionCallback, const +) + class ZapToken(BaseContract): + """docstring for ZapToken""" + + def __init__(self, options: NetworkProviderOptions = None): + options["artifact_name"] = "ZAP_TOKEN" + BaseContract.__init__(self, **options) + + async def balance_of(self, address: address) -> int: + + return self.contract.functions.balanceOf(address).call() + + async def send(self, to: address, amount: int, From: address, + gas_price: int, + gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None, + node=None) -> txid: + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + try: + + tx_hash = self.contract.functions.transfer( + to, amount).transact(tx_meta) + + if cb and node: + receipt = node.getTransactionReceipt(tx_hash) + logs = self.contract.events.Transfer( + ).processReceipt(receipt)[0] + + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + + return tx_hash.hex() + except Exception as e: + raise e + + async def allocate(self, to, amount: int, + gas_price: int, + gas: int = const.DEFAULT_GAS): + + tx_meta = {"gas": gas, "gasPrice": gas_price} + try: + + tx_hash = self.contract.functions.allocate( + to, amount).transact(tx_meta) + + return tx_hash.hex() + except Exception as e: + raise e + + async def approve(self, to: address, amount: int, + From: address, gas_price: int, + gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None, + node=None) -> txid: + + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + try: + + tx_hash = self.contract.functions.approve( + to, amount).transact(tx_meta) + + if cb and node: + receipt = node.getTransactionReceipt(tx_hash) + logs = self.contract.events.Approval( + ).processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + + return tx_hash.hex() + except Exception as e: + raise e + + async def transfer_from(self, to: address, From: address, amount: int, + gas_price: int, + gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None, + node=None) -> txid: + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + try: + + tx_hash = self.contract.functions.transferFrom( + From, to, amount).transact(tx_meta) + + if cb and node: + receipt = node.getTransactionReceipt(tx_hash) + logs = self.contract.events.Transfer( + ).processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + + return tx_hash.hex() + except Exception as e: + raise e + + async def allowance(self, owner: address, spender: address) -> int: + + return\ + self.contract.functions.allowance(owner, spender).call() + + async def finish_minting(self, cb: TransactionCallback = None, + node=None): + try: + tx_hash = self.contract.functions.finishMinting().transact() + + if cb and node: + receipt = node.eth.getTransactionReceipt(tx_hash) + logs = self.contract.events.MintFinished( + ).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 increase_approval(self, spender: address, added_value: int, + From: str, gas_price: int, + gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None, + node=None) -> txid: + + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + try: + + tx_hash = self.contract.functions.increaseApproval( + spender, added_value).transact(tx_meta) + + if cb and node: + receipt = node.getTransactionReceipt(tx_hash) + logs = self.contract.events.Approval( + ).processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + + return tx_hash.hex() + except Exception as e: + raise e + + async def decrease_approval(self, spender: address, subtracted_value: int, + From: str, gas_price: int, + gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None, + node=None) -> txid: + + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + try: + + tx_hash = self.contract.functions.decreaseApproval( + spender, subtracted_value).transact(tx_meta) + + if cb and node: + receipt = node.getTransactionReceipt(tx_hash) + logs = self.contract.events.Approval( + ).processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) - def __init__(self, NetworkProviderOptions): - 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}) - # if cb: - # #Event - # pass - # return promiEvent - - # async def allocate({to, amount,from, gasPrice, gas = Util.DEFAULT_GAS } : TransferType, cb = TransactionCallback): - # amount = toHex(amount) - # promiEvent = self.contract.allocate(to, amount),send({from, gas, gasPrice}) - # if cb: - # #Event - # pass - # return promiEvent - - # async def approve({to, amount,from, gasPrice, gas = Util.DEFAULT_GAS } : TransferType, cb = TransactionCallback): - # amount = toHex(amount) - # _success = self.contract.approve(to,ammount).send({from, gas, gasPrice}) - # if cd: - # #Event - # pass - # success = await _success - - # if (not success): - # raise Exception('Failed to approve Bondage transfer') - - # return success + return tx_hash.hex() + except Exception as e: + raise e diff --git a/tests/ZapToken/conftest.py b/tests/ZapToken/conftest.py new file mode 100644 index 00000000..9e424ac5 --- /dev/null +++ b/tests/ZapToken/conftest.py @@ -0,0 +1,144 @@ +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 = Artifacts["ZAP_TOKEN"] +coor_artifact = Artifacts["ZAPCOORDINATOR"] +_address = Web3.toChecksumAddress(abi["networks"]["31337"]["address"]) + + +@fixture(scope="module") +def w3(): + return _w3 + + +@fixture(scope="module") +def zt_contract(w3): + _zt_contract = w3.eth.contract(address=_address, + abi=abi["abi"]) + return _zt_contract + + +@fixture(scope="module") +def coor(): + _coor = _w3.eth.contract( + abi=coor_artifact['abi'], + address=coor_artifact['networks']["31337"]['address']) + return _coor + + +def _ZapToken(zt_contract, coor): + + mp = MonkeyPatch() + + 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.network_id = network_id or "1" + self.coordinator = coor or coordinator + self.address =\ + self.artifact["networks"][self.network_id]["address"] + self.contract = zt_contract + + mp.setattr("zap_token.BaseContract", MockBaseContract) + from zap_token import ZapToken + + try: + return(ZapToken({"network_id": "31337"})) + except Exception as e: + raise e + + +@fixture(scope="module") +def Zap_Token(): + zap_tok_obj = _ZapToken + yield zap_tok_obj + del zap_tok_obj + + +@fixture(scope="module") +def functions(reg_contract): + return reg_contract.functions + + +@fixture(scope="module") +def accounts(): + return _w3.eth.accounts + + +@fixture(scope="class") +def owner(accounts): + return accounts[0] + + +@fixture(scope="module") +def subscriber_1(accounts): + return accounts[1] + + +@fixture(scope="class") +def oracle(accounts): + return accounts[2] + + +@fixture(scope="module") +def subscriber_2(accounts): + return accounts[3] + + +@fixture(scope="module") +def provider_1(subscriber_1): + return {"pubkey": 101, + "title": '0x426c696365726f', + "address": subscriber_1, + "endpoint_params": ['param1', 'param2'], + "endpoint": 'Wiles', + "query": 'btcPrice', + "curve": Curve([3, 0, 0, 1, 1222]), + "broker": '0x0000000000000000000000000000000000000000' + } + + +@fixture(scope="module") +def provider_2(subscriber_2): + return {"pubkey": 103, + "title": '0x456e7a69616e', + "address": subscriber_2, + "endpoint_params": ['param1', 'param2'], + "endpoint": 'Jacobi', + "query": 'btcPrice', + "curve": Curve([1, 100, 1000]), + "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_Token, zt_contract, coor): + """ SetUp: ZapRegistry instance. + + This instance is cached and can be reused for + the remainder of the test. + """ + instance = Zap_Token(zt_contract, coor) + return instance diff --git a/tests/ZapToken/test_zaptoken.py b/tests/ZapToken/test_zaptoken.py new file mode 100644 index 00000000..2e7065a7 --- /dev/null +++ b/tests/ZapToken/test_zaptoken.py @@ -0,0 +1,133 @@ +from pytest import mark +from pprint import pprint + + +class Test_ZapToken: + """docstring for Test_ZapToken""" + + @mark.anyio + async def test_init(self, instance, w3): + print("\n\nTesting ZapToken object __init__") + assert instance + + @mark.anyio + async def test_increase_approval(self, instance, w3, provider_2): + tx_meta = {"From": provider_2["address"], + "gas_price": w3.eth.gas_price} + + tx_hash = await instance.increase_approval( + provider_2["address"], + 1000, **tx_meta) + assert tx_hash + + receipt = w3.eth.getTransactionReceipt(tx_hash) + logs = instance.contract.events.Approval( + ).processReceipt(receipt)[0] + + assert "error" not in logs + + print("\nTx Logs") + pprint(dict(logs)) + + # @mark.skip + @mark.anyio + async def test_allocate(self, instance, provider_1, w3): + tx_meta = {"gas_price": w3.eth.gas_price} + + tx_hash = await instance.allocate( + provider_1["address"], 100, **tx_meta) + assert tx_hash + + @mark.anyio + async def test_allowance(self, instance, + provider_1, provider_2, + w3): + allowance = await instance.allowance( + provider_1["address"], + provider_2["address"]) + + assert isinstance(allowance, int) + + @mark.anyio + async def test_balance_of(self, instance, provider_2): + balance = await instance.balance_of( + provider_2["address"]) + + assert balance + print("Token Balance: ", balance) + assert isinstance(balance, int) + assert balance > 0 + + @mark.anyio + async def test_decrease_approval(self, instance, provider_2, w3): + tx_meta = {"From": provider_2["address"], + "gas_price": w3.eth.gas_price} + + tx_hash = await instance.decrease_approval( + provider_2["address"], 100, **tx_meta) + assert(tx_hash) + + receipt = w3.eth.getTransactionReceipt(tx_hash) + logs = instance.contract.events.Approval( + ).processReceipt(receipt)[0] + + assert "error" not in logs + + print("\nTx Logs:") + pprint(dict(logs)) + + @mark.anyio + async def test_transfer(self, instance, provider_2, w3): + tx_meta = {"From": provider_2["address"], + "gas_price": w3.eth.gas_price} + + tx_hash = await instance.send( + provider_2["address"], + 100, **tx_meta) + assert tx_hash + + receipt = w3.eth.getTransactionReceipt(tx_hash) + logs = instance.contract.events.Transfer( + ).processReceipt(receipt)[0] + + assert "error" not in logs + + print("\nTx Logs:") + pprint(dict(logs)) + + @mark.anyio + async def test_transfer_from(self, instance, + provider_2, provider_1, + w3): + tx_meta = {"From": provider_2["address"], + "gas_price": w3.eth.gas_price} + + tx_hash = await instance.transfer_from( + to=provider_2["address"], + amount=20, **tx_meta) + assert tx_hash + + receipt = w3.eth.getTransactionReceipt(tx_hash) + logs = instance.contract.events.Transfer( + ).processReceipt(receipt)[0] + + assert "error" not in logs + + print("\nTx Logs:") + pprint(dict(logs)) + + @mark.skip("Leave to last; no way to revert once contracts are deployed.") + @mark.anyio + async def test_finish_minting(self, instance, w3): + print("\nTesting if ZapToken can stop minting") + tx_hash = await instance.finish_minting() + assert tx_hash + + receipt = w3.eth.getTransactionReceipt(tx_hash) + logs = instance.contract.events.MintFinished( + ).processReceipt(receipt)[0] + + assert "error" not in logs + + print("\nTx Logs:") + pprint(dict(logs)) From 49e6a60c646af8570cf7562c7a86b44a665f2b3e Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Thu, 13 May 2021 10:46:41 -0400 Subject: [PATCH 20/21] Added docs. --- tests/ZapToken/test_zaptoken.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/ZapToken/test_zaptoken.py b/tests/ZapToken/test_zaptoken.py index 2e7065a7..3ea2c871 100644 --- a/tests/ZapToken/test_zaptoken.py +++ b/tests/ZapToken/test_zaptoken.py @@ -3,7 +3,21 @@ class Test_ZapToken: - """docstring for Test_ZapToken""" + """ + Tests the ZapToken port. + + Tests the following ZapToken Methods: + - increase_approval + - allocate + - allowance + - balance_of + - decrease_approval + - transfer + - transfer_from + - finish_minting + + finish_minting can be tested once per test environment deployment. + """ @mark.anyio async def test_init(self, instance, w3): From 1bfe38d8387f65f6e003c381727cbbacb483ede2 Mon Sep 17 00:00:00 2001 From: Jamil Bousquet Date: Thu, 13 May 2021 14:08:00 -0400 Subject: [PATCH 21/21] Added docs. --- src/zap_token/__init__.py | 69 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/zap_token/__init__.py b/src/zap_token/__init__.py index 3cb79c1c..81ebca22 100644 --- a/src/zap_token/__init__.py +++ b/src/zap_token/__init__.py @@ -8,13 +8,33 @@ class ZapToken(BaseContract): - """docstring for ZapToken""" + """ + Represents an interface to the Zap Token ERC20 contract. + + Enables token transfers, balance lookups, and approvals. + + :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) + """ def __init__(self, options: NetworkProviderOptions = None): options["artifact_name"] = "ZAP_TOKEN" BaseContract.__init__(self, **options) async def balance_of(self, address: address) -> int: + """ + Get the Zap Token balance of a given address. + + :param {address} address Address to check + + :returns {Promise} Returns a Promise that will eventually resolve into a Zap balance (wei) + """ return self.contract.functions.balanceOf(address).call() @@ -23,6 +43,21 @@ async def send(self, to: address, amount: int, From: address, gas: int = const.DEFAULT_GAS, cb: TransactionCallback = None, node=None) -> txid: + """ + Transfers Zap from an address to another address. + + :param to: Address of the recipient + + :param amount: Amount of Zap to transfer (wei) + + :param from: Address of the sender + + :param gas: Sets the gas limit for this transaction (optional) + + :param cb: Callback for transactionHash event + + :returns a Coroutine that will eventually resolve into a transaction hash + """ tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} try: @@ -45,6 +80,21 @@ async def send(self, to: address, amount: int, From: address, async def allocate(self, to, amount: int, gas_price: int, gas: int = const.DEFAULT_GAS): + """ + Allocates Zap Token from the Zap contract owner to an address (ownerOnly). + + :param to: Address of the recipient + + :param amount: Amount of Zap to allocate (wei) + + :param from: Address of the sender (must be owner of the Zap contract) + + :param gas: Sets the gas limit for this transaction (optional) + + :param cb: Callback for transactionHash event + + :returns a Coroutine that will eventually resolve into a transaction hash + """ tx_meta = {"gas": gas, "gasPrice": gas_price} try: @@ -61,6 +111,22 @@ async def approve(self, to: address, amount: int, gas: int = const.DEFAULT_GAS, cb: TransactionCallback = None, node=None) -> txid: + """ + Approves the transfer of Zap Token from a holder to another account. + Enables the bondage contract to transfer Zap during the bondage process. + + :param to: Address of the recipient + + :param amount: Amount of Zap to approve (wei) + + :param from: Address of the sender + + :param gas: Sets the gas limit for this transaction (optional) + + :param cb: Callback for transactionHash event + + :returns a Coroutine that will eventually resolve into a transaction hash + """ tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} try: @@ -85,6 +151,7 @@ async def transfer_from(self, to: address, From: address, amount: int, gas: int = const.DEFAULT_GAS, cb: TransactionCallback = None, node=None) -> txid: + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} try: