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/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 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/Bondage/bondage.py b/src/Bondage/bondage.py deleted file mode 100644 index 00573184..00000000 --- a/src/Bondage/bondage.py +++ /dev/null @@ -1,104 +0,0 @@ -from BaseContract.base_contract import BaseContract -#from Registry.registry import Registry (in hopes this will get ported soon...) -from portedFiles.types import (BondageArgs, BondArgs, UnbondArgs, DelegateBondArgs, BondFilter, Filter, txid, NetworkProviderOptions, Constants, NumType, TransactionCallback) -from web3._utils import (utf8ToHex, toHex) - -class ZapBondage(BaseConstract): - - def __init__(self, NetworkProviderOptions): - super().__init__(self, NetworkProviderOptions or {}, { artifactName: 'Bondage'}) - - async def bond(self, bond_args: BondArgs , callback: TransactionCallback): - assert(dots and dots > 0, 'Dots to bond must be greater than 0.') - dots = toHex(bond_args['dots']) - #getEndpointBroker() is part of the Registry code (currently missing) - broker = await self.contract.getEndpointBroker(bond_args['provider'], utf8ToHex(bond_args['endpoint'])) - if broker != const.NULL_ADDRESS: - if bond_args['frm'] != broker: - raise Error('Broker address {broker} needs to call delegate bonding'.format(broker)) - promiEvent = self.contract.bond( - bond_args['provider'], - utf8ToHex(bond_args['endpoint']), - dots) - .send({ bond_args['frm'], bond_args['gas'], bond_args['gasPrice'] }) - if callback: - #Add events - pass - return promiEvent - - async def delegateBond(self, delegate_bond_args: DelegateBondArgs, callback: TransactionCallback): - assert(dots and dots > 0, 'Dots to bond must be greater than 0.') - dots = toHex(delegate_bond_args['dots']) - broker = await self.contract.getEndpointBroker(delegate_bond_args['provider'], utf8ToHex(delegate_bond_args['endpoint'])) - if broker != const.NULL_ADDRESS: - if delegate_bond_args['frm'] != broker: - raise Error('Broker address {broker} needs to call delegate bonding for this endpoint'.format(broker)) - promiEvent = self.contract.delegateBond( - delegate_bond_args['subscriber'], - delegate_bond_args['provider'], - utf8ToHex(delegate_bond_args['endpoint']), - dots) - .send({ delegate_bond_args['frm'], delegate_bond_args['gas'], delegate_bond_args['gasPrice'] }) - if callback: - #Add events - pass - return promiEvent - - async def unbond(self, unbond_args: UnbondArgs , callback: TransactionCallback): - assert(dots and dots > 0, 'Dots to unbond must be greater than 0.') - dots = toHex(unbond_args['dots']) - #getEndpointBroker() is part of the Registry code (currently missing) - broker = await self.contract.getEndpointBroker(unbond_args['provider'], utf8ToHex(unbond_args['endpoint'])) - if broker != const.NULL_ADDRESS: - if unbond_args['frm'] != broker: - raise Error('Broker address {broker} needs to call unbonding for this endpoint'.format(broker)) - promiEvent = self.contract.unbond( - unbond_args['provider'], - utf8ToHex(unbond_args['endpoint']), - dots) - .send({ unbond_args['frm'], unbond_args['gas'], unbond_args['gasPrice'] }) - if callback: - #Add events - pass - return promiEvent - - - #GETTERS - async def getBoundDots(self, bondage_args: BondageArgs): - return await self.contract.getBoundDots(bondage_args['subscriber'], bondage_args['provider'],utf8ToHex(bondage_args['endpoint'])) - - async def getDotsLimit(self, bondage_args: BondageArgs): - return await self.contract.dotLimit(bondage_args['provider'], utf8ToHex(bondage_args['endpoint'])) - - async def getDotsIssued(self, bondage_args: BondageArgs): - return await self.contract.getDotsIssued(bondage_args['provider'], utf8ToHex(bondage_args['endpoint'])) - - async def getBrokerAddress(self, bondage_args: BondageArgs): - return await self.contract.getEndpointBroker(bondage_args['provider'], utf8ToHex(bondage_args['endpoint'])) - - async def getZapBound(self, bondage_args: BondageArgs): - return await self.contract.getZapBound(bondage_args['provider'], utf8ToHex(bondage_args['endpoint'])) - - async def getNumEscrow(self, bondage_args: BondageArgs): - return await self.contract.getNumEscrow(bondage_args['subscriber'], bondage_args['provider'], bondage_args['endpoint']) - - - #EVENTS (Still not sure on these, I'm unaware of what the .events method is referring to) - def listen(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.allEvents(filters, { 'fromBlock': 0, 'toBlock': 'latest' }, callback) - - def listenBound(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Bound(filters, { 'toBlock': 'latest' }, callback) - - def listenBound(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Unbond(filters, {'toBlock': 'latest' }, callback) - - def listenEscrowed(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Escrowed(filters, { 'toBlock': 'latest' }, callback) - - def listenReleased(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Released(filters, {'toBlock': 'latest' }, callback) - - def listenReturned(filters: BondFilter = {}, callback: TransactionCallback): - self.contract.events.Returned(filters, { 'toBlock': 'latest' }, callback) - 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/__init__.py b/src/bondage/__init__.py new file mode 100644 index 00000000..9fbe818f --- /dev/null +++ b/src/bondage/__init__.py @@ -0,0 +1,357 @@ +from asyncio import sleep + +from web3 import Web3 + +from base_contract import BaseContract +from registry import ZapRegistry +from zaptypes import ( + BondFilter, txid, NetworkProviderOptions, + TransactionCallback, const, address +) + + +class ZapBondage(BaseContract): + """ Provides an interface to the Bondage contract. + Enables bonding and unbonding to Oracles. + """ + + def __init__(self, options: NetworkProviderOptions): + options["artifact_name"] = "BONDAGE" + BaseContract.__init__(self, **options or {}) + + async def bond(self, provider: address, endpoint: str, + dots: int, From: address, gas_price: int, + gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None) -> txid: + """ Bonds a number of dots from a subscriber to a provider's endpoint. + + Note: this requires that at least zapNum has been approved from + the subscriber to be transferred by the Bondage contract. + + :param provider: Provider's address + + :param dots: number of dots to bond to this provider + + :param From: Subscriber's owner (0 broker) or broker's address + + :param gas: Sets the gas limit for this transaction + + :param cb: Callback for transactionHash event + + :returns + Coroutine that eventually resolves into a transaction hash. + """ + assert (dots and dots > 0),\ + "Dots to bond must be greater than 0." + + payload = {"oracleAddress": provider, + "endpoint": Web3.toBytes(text=endpoint)} + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + + broker =\ + self.contract.functions.getEndpointBroker(**payload).call() + + if broker != const.NULL_ADDRESS: + if From != broker: + raise Exception(f"Broker address {broker} needs to call delegate bonding") + + payload["numDots"] = dots + + try: + await sleep(0) + tx_hash = self.contract.functions.bond( + **payload).transact(tx_meta) + if cb: + receipt = tx_hash + logs = self.contract.events.Bound().processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + return tx_hash.hex() + except Exception as e: + print(e) + + async def delegate_bond(self, provider: address, subscriber: address, + endpoint: str, dots: int, From: address, + gas_price: int, gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None) -> txid: + """ Bonds a given number of dots from an account to a subscriber. + + This would be used to bond to a provider + on behalf of another account, such as a smart contract. + + :param provider: Provider's address + + :param endpoint: Data endpoint of the provider + + :param dots: Number of dots to bond to this provider + + :param subscriber: Address of the intended holder of the dots + + :param From: Address of the data subscriber + + :param gas: Sets the gas limit for this transaction + + :param cb: Callback for transactionHash event + + :returns + Coroutine that eventually resolves into a transaction hash. + """ + + assert (dots and dots > 0),\ + "Dots to bond must be greater than 0." + + payload = {"oracleAddress": provider, + "endpoint": Web3.toBytes(text=endpoint)} + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + + broker =\ + self.contract.functions.getEndpointBroker(**payload).call() + + if broker != const.NULL_ADDRESS: + if From != broker: + raise Exception(f"Broker address {broker} needs to call delegate bonding") + + payload.update({"numDots": dots, "holderAddress": subscriber}) + + try: + await sleep(0) + tx_hash = self.contract.functions.delegateBond( + **payload).transact(tx_meta) + + if cb: + receipt = tx_hash + logs = self.contract.events.Bound().processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + + return tx_hash.hex() + except Exception as e: + print(e) + + async def unbond(self, provider: address, + endpoint: str, dots: int, From: address, + gas_price: int, gas: int = const.DEFAULT_GAS, + cb: TransactionCallback = None) -> txid: + """ Unbonds a given number of dots. + + It unbonds from a provider's endpoint and transfers the + appropriate amount of Zap to the subscriber. + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :param dots: The number of dots to unbond from the contract + + :param From : Address of the data subscriber + + :param gas: Sets the gas limit for this transaction (optional) + + :param cb: Callback for transactionHash event + + :returns + Coroutine that eventually resolves into a transaction hash. + """ + + assert (dots and dots > 0),\ + "Dots to unbond must be greater than 0." + + payload = {"oracleAddress": provider, "endpoint": Web3.toBytes(text=endpoint)} + tx_meta = {"from": From, "gas": gas, "gasPrice": gas_price} + + broker =\ + self.contract.functions.getEndpointBroker(**payload).call() + + if broker != const.NULL_ADDRESS: + if From != broker: + raise Exception(f"Broker address {broker} needs to call delegate bonding") + + payload["numDots"] = dots + + try: + await sleep(0) + tx_hash = self.contract.functions.unbond( + **payload).transact(tx_meta) + + if cb: + receipt = tx_hash + logs = self.contract.events.Bound().processReceipt(receipt)[0] + if "error" in logs: + cb(logs["error"]) + cb(None, logs["args"]["transactionHash"]) + + return tx_hash + except Exception as e: + print(e) + + """ Getters + """ + + async def get_bound_dots(self, subscriber: address, provider: address, + endpoint: str) -> int: + """ Gets the number of dots that are bounded to a provider's endpoint + for the current subscriber. + + :param subscriber: Address of the data subscriber + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :returns + A Coroutine that resolves to the number of + bound dots to this provider's endpoint + """ + await sleep(0) + return self.contract.functions.getBoundDots( + subscriber, provider, Web3.toBytes(text=endpoint)).call() + + async def calc_zap_for_dots(self, provider: address, + endpoint: str, dots: int): + """ Calculates the amount of Zap required to bond + a given number of dots to a provider's endpoint. + + :param provider: Address of the data provider + + :param endpoint: Endpoint to calculate zap + + :param dots: Number of dots to calculate the price (in Zap) for + + :returns + A Coroutine that resolves to the price (in Zap) + for the given number of dots + """ + await sleep(0) + return self.contract.functions.calcZapForDots( + provider, Web3.toBytes(text=endpoint), dots).call() + + async def current_cost_of_dot(self, provider: address, + endpoint: str, dots: int) -> int: + """ Calculates the amount of Zap required to bond + a given number of dots to a provider's endpoint. + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :param dots: dots that subscriber want to use + + :returns + A Coroutine that resolves into the price (in Zap wei) + for next x dots to bond + """ + await sleep(0) + return self.contract.functions.currentCostOfDot( + provider, Web3.toBytes(text=endpoint), dots).call() + + async def get_dots_limits(self, provider: address, endpoint: str) -> int: + """ Get Maximum dots that can be bound for an endpoint of a provider + + :param provider: Provider's address + + :param endpoint: Provider's endpoint to get dots limit + + :returns + A Coroutine that resolves into the number + of maximum dots that can be bound + + """ + await sleep(0) + return self.contract.functions.dotLimit( + provider, Web3.toBytes(text=endpoint)).call() + + async def get_dots_issued(self, provider: address, endpoint: str) -> int: + """ Gets the total number of dots that have been issued + by a provider's endpoint. + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :returns A Coroutine that resolves into the number of dots issued + + """ + await sleep(0) + return self.contract.functions.getDotsIssued( + provider, Web3.toBytes(text=endpoint)).call() + + async def get_broker_address(self, provider: address, endpoint: str): + """ Get Broker address for this provider's endpoint + return NULL_ADDRESS if there is none + + :param provider: Provider's Address + + :param endpoint: Provider's endpoint to get Broker's address + + :returns + A Coroutine that resolves into a broker's + address for this endpoint, null address if none + + """ + sleep(0) + return await\ + self.contract.functions.getEndpointBroker( + provider, Web3.toBytes(text=endpoint)).call() + + async def get_zap_bound(self, provider: address, endpoint: str) -> int: + """ Gets the total amount of Zap that has been bonded + to a provider's endpoint. + + :param provider: Address of the data provider + + :param endpoint: Data endpoint of the provider + + :returns A Coroutine that resolves into the amount of Zaps (wei) + that are bound to this endpoint + + """ + await sleep(0) + return self.contract.functions.getZapBound( + provider, Web3.toBytes(text=endpoint)).call() + + async def get_num_escrow(self, provider: address, subscriber: address, + endpoint: str) -> int: + """ Get Number of dots escrow + + :param provider + + :param endpoint + + :param subscriber + + :returns A Coroutine that resolve into the number of escrow dots + + """ + await sleep(0) + return self.contract.functions.getNumEscrow( + subscriber, provider, Web3.toBytes(text=endpoint)).call() + + """ Events + """ + + def listen(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.allEvents( + filters, {"fromBlock": 0, "toBlock": "latest"}, cb) + + def listen_bound(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Bound(filters, {"toBlock": "latest"}, cb) + + def listen_unbound(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Unbond(filters, {"toBlock": "latest"}, cb) + + def listen_escrow(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Escrowed(filters, {"toBlock": "latest"}, cb) + + def listen_released(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Released(filters, {"toBlock": "latest"}, cb) + + def listen_returned(self, cb: TransactionCallback, + filters: BondFilter = {}) -> None: + self.contract.events.Returned(filters, {"toBlock": "latest"}, cb) diff --git a/src/registry/__init__.py b/src/registry/__init__.py new file mode 100644 index 00000000..a424bb2e --- /dev/null +++ b/src/registry/__init__.py @@ -0,0 +1,372 @@ +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 + + NetworkProviderOptions has the following keyword arguments: + + :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=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. + + """ + + try: + 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) + + 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=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}) + 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, end_point, term, + From, gas_price, + cb: TransactionCallback = None, + broker=const.NULL_ADDRESS, + gas=const.DEFAULT_GAS) -> txid: + """""" + await sleep(0.247) + + try: + tx_hash: txid = self.contract.functions.initiateProviderCurve( + Web3.toBytes(text=end_point), + term, broker).transact( + {"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, 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.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) 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/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/Bondage/conftest.py b/tests/Bondage/conftest.py new file mode 100644 index 00000000..aa7f6253 --- /dev/null +++ b/tests/Bondage/conftest.py @@ -0,0 +1,151 @@ +from pytest import MonkeyPatch, fixture + +from web3 import Web3 + +from os.path import join, realpath +from sys import path +path.insert(0, realpath(join(__file__, "../../../src/"))) + +from artifacts.src import Artifacts +from zap_token.curve import Curve + +_w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) +_abi = {"bondage": Artifacts["BONDAGE"], + "zap_token": Artifacts["ZAP_TOKEN"]} +coor_artifact = Artifacts["ZAPCOORDINATOR"] + +""" Arrange/SetUp Section +""" + + +@fixture(scope="module") +def w3(): + return _w3 + + +@fixture(scope="module") +def address(w3): + return {name: Web3.toChecksumAddress( + abi["networks"]["31337"]["address"]) + for name, abi in _abi.items()} + + +@fixture(scope="module") +def bond_contract(w3, address): + _bond_contract = w3.eth.contract( + address=address["bondage"], + abi=_abi["bondage"]["abi"]) + + return _bond_contract + + +@fixture(scope="class") +def zt_contract(w3, address): + _zt_contract = w3.eth.contract( + address=address["zap_token"], + abi=_abi["zap_token"]["abi"]) + + return _zt_contract + + +@fixture(scope="module") +def functions(bond_contract): + return bond_contract.functions + + +@fixture(scope="module") +def accounts(w3): + return w3.eth.accounts + + +@fixture(scope="module") +def owner(accounts): + return accounts[0] + + +@fixture(scope="module") +def subscriber(accounts): + return accounts[1] + + +@fixture(scope="module") +def oracle(accounts): + return accounts[8] + + +@fixture(scope="module") +def broker(accounts): + return accounts[3] + + +@fixture(scope="module") +def escrower(accounts): + return accounts[4] + + +def _ZapBondage(bond_contract): + + mp = MonkeyPatch() + # same as setting the return_value of a mock obj + + class MockBaseContract: + def __init__(self, artifact_name, artifact_dir=None, network_id=None, + network_provider=None, coordinator=None, + address=None, web3=None): + + self.name = artifact_name + self.artifact = Artifacts[artifact_name] + coor_artifact = Artifacts["ZAPCOORDINATOR"] + self.provider = web3 or _w3 + self.network_id = network_id or "1" + self.coordinator = self.provider.eth.contract( + abi=coor_artifact['abi'], + address=coor_artifact['networks'][self.network_id]['address']) + + self.address =\ + self.artifact["networks"][self.network_id]["address"] + self.contract = bond_contract + + mp.setattr("bondage.BaseContract", MockBaseContract) + from bondage import ZapBondage + + try: + return(ZapBondage({"network_id": "31337"})) + except Exception as e: + raise e + + +@fixture(scope="module") +def Zap_Bondage(): + + zap_bond_obj = _ZapBondage + yield zap_bond_obj + del zap_bond_obj + + +@fixture(scope="module") +def provider(oracle): + return {"pubkey": 108, + "title": '0x4269616e6361', + "address": oracle, + "endpoint_params": ['param1', 'param2'], + "endpoint": 'Fibonacci', + "query": 'btcPrice', + "curve": Curve([2, 5000, 2000, 1000, + 2, 0, 3000, 10000]), + "broker": '0x0000000000000000000000000000000000000000' + } + + +@fixture(scope="module") +def anyio_backend(): + """ Ensures anyio uses the default, pytest-asyncio plugin + for running async tests + """ + return 'asyncio' + + +@fixture(scope="class") +def instance(Zap_Bondage, bond_contract): + _instance = Zap_Bondage(bond_contract) + return _instance diff --git a/tests/Bondage/test_bondage.py b/tests/Bondage/test_bondage.py new file mode 100644 index 00000000..c71ce3ae --- /dev/null +++ b/tests/Bondage/test_bondage.py @@ -0,0 +1,194 @@ +from pytest import mark, fixture + +from web3 import Web3 + +from os.path import join, realpath +from sys import path +path.insert(0, realpath(join(__file__, "../../../src/"))) + +from zap_token.curve import Curve +from artifacts.src import Artifacts +from zaptypes import const + + +class TestBondage: + """docstring for TestBondage""" + + global tx_payload, tx_del_payload + tx_payload = {} + tx_del_payload = {} + + global required + required = 0 + + def test_init(self, instance, bond_contract, subscriber, + oracle, provider, w3): + + global tx_payload + + assert instance + assert instance.contract.address == bond_contract.address + + tx_payload = {"provider": oracle, "endpoint": provider["endpoint"], + "From": subscriber, "gas_price": w3.eth.gas_price} + + @fixture(scope="class", autouse=True) + def prepare_tokens(self, instance, zt_contract, + subscriber, owner, broker, w3): + bondage_owner = instance.contract.address + zt_contract.functions.allocate( + owner, 1500000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + zt_contract.functions.allocate( + subscriber, 50000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + zt_contract.functions.allocate( + broker, 50000000000000000000000000000).transact( + {"from": owner, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + zt_contract.functions.approve( + bondage_owner, 1000000000000000000000000000000).transact( + {"from": subscriber, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + zt_contract.functions.approve( + bondage_owner, 1000000000000000000000000000000).transact( + {"from": broker, "gas": const.DEFAULT_GAS, + "gasPrice": w3.eth.gas_price}) + + @mark.anyio + async def test_bond(self, instance): + + print("\n\nTesting bond") + tx_hash = await instance.bond(dots=999, **tx_payload) + + assert isinstance(tx_hash, str) + print("✅ Passed\n") + + print("\nTesting get_bound_dots") + bonded = await instance.get_bound_dots( + tx_payload["From"], tx_payload["provider"], + tx_payload["endpoint"]) + print("✅ Passed\n") + + print("\nConfirming dots were bonded") + assert isinstance(bonded, int) + assert bonded == 999 + + @mark.anyio + async def test_delegate_bond(self, instance, escrower): + + print("\n\nTesting delegate_bond") + tx_hash = await instance.delegate_bond( + subscriber=escrower, dots=999, **tx_payload) + + assert isinstance(tx_hash, str) + print("✅ Passed\n") + + print("\nConfirming dots were bonded") + bonded = await instance.get_bound_dots( + escrower, tx_payload["provider"], + tx_payload["endpoint"]) + + assert isinstance(bonded, int) + assert bonded == 999 + + @mark.anyio + async def test_calc_zap_for_dots(self, instance): + global required + required = await\ + instance.calc_zap_for_dots( + tx_payload["provider"], + tx_payload["endpoint"], + 5) + + assert isinstance(required, int) + assert required > 0 + + @mark.anyio + async def test_current_cost_of_dot(self, instance): + cost = await\ + instance.current_cost_of_dot( + tx_payload["provider"], + tx_payload["endpoint"], + 5) + + assert isinstance(cost, int) + assert cost > 0 + + @mark.anyio + async def test_get_dots_limits(self, instance): + limit = await\ + instance.get_dots_limits( + tx_payload["provider"], + tx_payload["endpoint"]) + + assert isinstance(limit, int) + assert limit > 0 + + @mark.anyio + async def test_get_dots_issued(self, instance, escrower): + issued = await\ + instance.get_dots_issued( + tx_payload["provider"], + tx_payload["endpoint"]) + + bonded = await instance.get_bound_dots( + tx_payload["From"], tx_payload["provider"], + tx_payload["endpoint"]) + + bonded_del = await instance.get_bound_dots( + escrower, tx_payload["provider"], + tx_payload["endpoint"]) + + print("\n\nTesting get_bound_dots") + assert isinstance(issued, int) + assert issued > 0 + print("✅ Passed\n") + print("\nConfirming dots issued == total bonded") + assert issued == bonded + bonded_del + print("Dots issued: ", issued) + + @mark.anyio + async def test_get_zap_bound(self, instance): + zap_bonded = await\ + instance.get_zap_bound( + tx_payload["provider"], + tx_payload["endpoint"]) + + assert isinstance(zap_bonded, int) + print("Num of Zap bonded", zap_bonded) + assert zap_bonded > 0 + + @mark.anyio + async def test_get_num_escrow(self, instance, escrower): + + escrowed = await\ + instance.get_num_escrow( + tx_payload["provider"], + escrower, + tx_payload["endpoint"]) + + assert isinstance(escrowed, int) + print("Num Escrowed: ", escrowed) + + bonded = await instance.get_bound_dots( + tx_payload["From"], tx_payload["provider"], + tx_payload["endpoint"]) + + bonded_del = await instance.get_bound_dots( + escrower, tx_payload["provider"], + tx_payload["endpoint"]) + + print("# of bonded dots: ", bonded) + print("# of bonded dots delegated: ", bonded_del) + + print("\nTesting unbond") + await instance.unbond(dots=bonded, **tx_payload) + tx_payload["From"] = escrower + await instance.unbond(dots=bonded_del, **tx_payload) 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..9529af8c --- /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 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 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): + """ 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..6e79bd78 --- /dev/null +++ b/tests/Registry/test_registry.py @@ -0,0 +1,240 @@ +""" 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): + opt = {"public_key": pubkey, "title": title, + "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, + endpoint, curve_values): + term = curve_values + opts = {"end_point": endpoint, "term": term, + "From": account, "gas_price": 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 + + @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)