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/src/ZapToken/Curve/__init__.py b/src/ZapToken/Curve/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/ZapToken/Zaptoken.py b/src/ZapToken/Zaptoken.py deleted file mode 100644 index 2f24b2b9..00000000 --- a/src/ZapToken/Zaptoken.py +++ /dev/null @@ -1,46 +0,0 @@ -from base_contract import BaseContract -from BaseContract import utils -from portedFiles.types import (TransferType, address, txid, NetworkProviderOptions, TransactionCallback, NumType) -#from web3._utils. import (toHex) - -class ZapToken(BaseConstract): - - 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 - - - - - 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 36448f38..561d738d 100644 --- a/src/Artifacts/contracts/Arbiter.json +++ b/src/artifacts/contracts/Arbiter.json @@ -419,7 +419,7 @@ "transactionHash": "0xd891f92a9be7d2cb45ff46bbdfdf1cf20cc9957a7f3137905e105abf24e01bc1" }, "31337": { - "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" + "address": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0" } } } \ No newline at end of file diff --git a/src/Artifacts/contracts/Bondage.json b/src/artifacts/contracts/Bondage.json similarity index 99% rename from src/Artifacts/contracts/Bondage.json rename to 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/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 99% rename from src/Artifacts/contracts/Dispatch.json rename to 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/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 88% rename from src/Artifacts/contracts/TokenDotFactory.json rename to src/artifacts/contracts/TokenDotFactory.json index f3cf2b6e..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" } ], @@ -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": { @@ -272,8 +294,14 @@ "42": { "events": {}, "links": {}, - "address": "0xf108237201E6EE3906f19390C699dA4d1495040F", + "address": "0xb0Bc8151Bcd5824359298587395655923F4c6AcB", "transactionHash": "0x52a4cdc19fdb99c432317d14b4b27254c430998fc8baa4c9a56bcc6e9915f1eb" + }, + "31337": { + "events": {}, + "links": {}, + "address": "0x809d550fca64d94bd9f66e60752a544199cfac3d", + "transactionHash": "0xcf5a0d23a80b4109afdc0bce7e496c6bd7b8491c683e9e5ebadd4e173da64e6c" } } } \ No newline at end of file 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 96% rename from src/Artifacts/contracts/ZapToken.json rename to src/artifacts/contracts/ZapToken.json index c6eb2c93..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": { @@ -422,6 +422,12 @@ "links": {}, "address": "0x11616e60d1b62633e02f044f155c9e03ff843974", "transactionHash": "0x995123b07bdd859f0edfef77c6f2842b8b32f57c586d591a6f1fff258b2c11f8" + }, + "31337": { + "events": {}, + "links": {}, + "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "transactionHash": "0xfa432264f28f3e8f3f151b9f834e1f6096d74b397bd5254cb883ef949aa5ad97" } } } \ No newline at end of file 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/Artifacts/src/tests/__init__.py b/src/artifacts/src/tests/__init__.py similarity index 100% rename from src/Artifacts/src/tests/__init__.py rename to src/artifacts/src/tests/__init__.py diff --git a/src/Artifacts/src/tests/test_artifacts.py b/src/artifacts/src/tests/test_artifacts.py similarity index 100% rename from src/Artifacts/src/tests/test_artifacts.py rename to src/artifacts/src/tests/test_artifacts.py diff --git a/src/BaseContract/base_contract.py b/src/base_contract/__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/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 new file mode 100644 index 00000000..64a2c27c --- /dev/null +++ b/src/registry/__init__.py @@ -0,0 +1,385 @@ +from web3 import Web3 +from asyncio import sleep +from typing import Optional, List + +from base_contract import BaseContract +from zap_token.curve import Curve +from zaptypes import ( + Filter, address, + NetworkProviderOptions, const, TransactionCallback, txid +) + + +class ZapRegistry(BaseContract): + """ This contract manages Providers and Curve registration + + NetworkProviderOptions: Dictionary object containing options for + BaseContract init + + :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: Optional[NetworkProviderOptions] = {}): + options["artifact_name"] = "REGISTRY" + BaseContract.__init__(self, **options) + + """ + Registry storage calls for all providers + """ + + async def get_all_providers(self) -> list: + """Get all providers in Registry Contract. + + :returns a list of oracles once async is fulfilled + """ + await sleep(3) + return self.contract.functions.getAllOracles().call() + + async def get_provider_address_by_index(self, index: int) -> str: + """ Look up provider's address by its index in registry storage + + :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() + + """ + Provider specific calls + """ + + async def initiate_provider(self, public_key: str, title: str, + From: address, + gas_price, + gas=const.DEFAULT_GAS, + cb: Optional[TransactionCallback] = None + ) -> txid: + """ + Initiates a brand endpoint in the Registry contract, + creating an Oracle entry if need be. + + :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. + + """ + + 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( + **payload).transact(tx_meta) + if cb: + 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: + print(e) + + async def get_provider_publickey(self, provider: address) -> int: + """ Get a provider's public key from the registry contract. + + :param provider: The address of this provider + + :returns the public key number. + """ + await sleep(0.8) + return self.contract.functions.getProviderPublicKey(provider).call() + + async def get_provider_title(self, provider: address) -> str: + """ Get a provider's title from the Registry contract. + + :param address: The address of this provider. + + :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, + gas_price, + gas=const.DEFAULT_GAS, + cb: TransactionCallback = None) -> txid: + """ Set the new provider's title + + :param From: The address of this provider + + :param title: The new title of this provider + + :param cb: Callback for transactionHash event + + """ + try: + await sleep(1.4) + tx_hash: txid = self.contract.functions.setProviderTitle( + Web3.toBytes(text=title)).transact( + {"from": From, "gas": gas, "gasPrice": gas_price}) + cb(None, tx_hash) + + return tx_hash.hex() + except Exception as e: + return(str(e)) + + async def is_provider_initiated(self, provider: address) -> bool: + """ 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. + """ + 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 + + :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 + """ + 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 + + :param provider: The address of the provider + + :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) -> List[str]: + """ Get the endpoints of a given provider + + :param 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 + + """ + Provider's specific endpoint calls + """ + + async def initiate_provider_curve(self, endpoint, term, + From, gas_price, + cb: TransactionCallback = None, + broker=const.NULL_ADDRESS, + gas=const.DEFAULT_GAS) -> txid: + """""" + 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( + **payload).transact(tx_meta) + if cb: + 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)) + + 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": gas_price}) + if cb: + cb(None, tx_hash) + return tx_hash.hex() + except Exception as e: + print(e) + + async def get_provider_curve(self, provider: address, endpoint: str): + await sleep(0.8) + terms: list = self.contract.functions.getProviderCurve( + provider, Web3.toBytes(text=endpoint)).call() + + return Curve([int(t) for t in terms]) + + def encode_params(self, endpoint_params: list = [], ): + pars = endpoint_params + hex_params =\ + [el if el.find('0x') == 0 else Web3.toHex(text=el) for el in pars] + bytes_params =\ + [Web3.toBytes(hexstr=hex_p) for hex_p in hex_params] + params = [] + + from math import ceil + for element in bytes_params: + if len(element) <= 32: + params.append(Web3.toHex(element)) + continue + chunks_len = ceil((len(element) + 2) / 32) + param_bytes_w_len = [0, chunks_len].extend(element) + for i in range(0, chunks_len): + start = i * 32 + end = start + 32 + params.append(Web3.toHex(param_bytes_w_len[start:end])) + return params + + def decode_params(self, raw_params: List[str] = []): + bytes_params =\ + [Web3.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(Web3.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(Web3.toHex(raw_bytes)) + + 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: str, From: address, + gas_price: 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 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 + """ + params = self.encode_params(endpoint_params) + + try: + await sleep(0.53) + tx_hash = self.contract.functions.setEndpointParams( + Web3.toBytes(text=endpoint), params).transact( + {"from": From, "gas": gas, "gasPrice": gas_price}) + if cb: + cb(None, tx_hash) + return tx_hash.hex() + except Exception as e: + print(e) + + 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): + await sleep(0.2) + unset: bool = self.contract.functions.getCurveUnset( + provider, + Web3.toBytes(text=endpoint)).call() + + return not unset + + """ Events + """ + from typing import Callable + + async def listen(self, callback: Callable[..., None]): + sleep(3) + self.contract.events._events(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) diff --git a/src/zap_token/__init__.py b/src/zap_token/__init__.py new file mode 100644 index 00000000..81ebca22 --- /dev/null +++ b/src/zap_token/__init__.py @@ -0,0 +1,240 @@ +from asyncio import sleep + +from base_contract import BaseContract +from zaptypes import ( + address, txid, NetworkProviderOptions, + TransactionCallback, const +) + + +class ZapToken(BaseContract): + """ + 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() + + async def send(self, to: address, amount: int, From: address, + gas_price: int, + 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: + + 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): + """ + 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: + + 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: + """ + 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: + + 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"]) + + return tx_hash.hex() + except Exception as e: + raise e 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/zaptypes/__init__.py b/src/zaptypes/__init__.py new file mode 100644 index 00000000..794d5a43 --- /dev/null +++ b/src/zaptypes/__init__.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(int(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 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/README.md b/tests/README.md new file mode 100644 index 00000000..c5cfa7cc --- /dev/null +++ b/tests/README.md @@ -0,0 +1,28 @@ +# 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 +# install anyio to run async tests and web3 test pack +pip3 install anyio web3[tester] +``` +## 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). + +## 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). 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 diff --git a/tests/Registry/conftest.py b/tests/Registry/conftest.py new file mode 100644 index 00000000..f050c1aa --- /dev/null +++ b/tests/Registry/conftest.py @@ -0,0 +1,195 @@ +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 import Artifacts +from zap_token.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 w3(): + return _w3 + + +@fixture(scope="module") +def reg_contract(w3): + _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): + """ 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.BaseContract", MockBaseContract) + from 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 new file mode 100644 index 00000000..d4f7003f --- /dev/null +++ b/tests/Registry/test_registry.py @@ -0,0 +1,242 @@ +""" 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 mark + +from web3 import Web3 + +from os.path import join, realpath +from sys import path +path.insert(0, realpath(join(__file__, "../../../src/"))) + +from zap_token.curve import Curve +from artifacts.src import Artifacts + + +""" pytest section +""" + + +class TestRegistry: + """ Tests the initialization and functionality of Zap's Registry Contract + """ + + 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, title, w3): + opt = {"public_key": pubkey, "title": title, + "From": account, "gas": 4 * 10**5, + "gas_price": w3.eth.gas_price} + 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, w3): + title_test = "REGISTRY" + + tx = await instance.set_provider_title(account, title_test, w3.eth.gas_price) + + 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, + endpoint, curve_values, w3): + term = curve_values + opts = {"endpoint": endpoint, "term": term, + "From": account, "gas_price": w3.eth.gas_price} + + 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 + + @mark.anyio + async def test_get_provider_curve(self, instance, + 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, + endpoint, + account): + e = endpoint + tx = await instance.clear_endpoint( + e, 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): +# """ Will handle awaitables from contract calls/tx +# """ +# await co_mock + +""" python "direct calls" section +""" + +# 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()))) + +# pprint(zap_reg_obj.contract.functions.getAllOracles.__dict__) + +# def test_init(): +# assert isinstance(_ZapRegistry(), ZapRegistry) 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..3ea2c871 --- /dev/null +++ b/tests/ZapToken/test_zaptoken.py @@ -0,0 +1,147 @@ +from pytest import mark +from pprint import pprint + + +class 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): + 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))