Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Towards #13: add unit tests #17

Merged
merged 14 commits into from
Aug 4, 2023
Merged
2 changes: 1 addition & 1 deletion pdr_backend/utils/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
from eth_account.signers.local import LocalAccount
from eth_keys import KeyAPI
from eth_keys.backends import NativeECCBackend
from sapphire_wrapper import wrapper

from pathlib import Path
from web3 import Web3, HTTPProvider, WebsocketProvider
from web3.middleware import construct_sign_and_send_raw_middleware
from os.path import expanduser
from sapphire_wrapper import wrapper
import artifacts # noqa

from pdr_backend.utils.constants import (
Expand Down
229 changes: 171 additions & 58 deletions pdr_backend/utils/subgraph.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,123 @@
"""
From this ref...
https://github.com/oceanprotocol/ocean-subgraph/pull/678 "Predictoor support")
... here's an example query:

query {
predictContracts{
id
token{
name
}
secondsPerEpoch
secondsPerSubscription
truevalSubmitTimeout
block
eventIndex
slots{
id
predictions{
id
user {
id
}
stake
payout {
id
predictedValue
trueValue
payout
}
}
trueValues{
trueValue
floatValue
txId
}
revenue
revenues{

amount
txId
}


}
subscriptions{

user {
id
}
expireTime
txId
}
}

}
"""


import os
import requests
from typing import Optional, Dict

from enforce_typing import enforce_types
from web3 import Web3

@enforce_types
def key_to_725(key:str):
key725 = Web3.keccak(key.encode("utf-8")).hex()
return key725


@enforce_types
def value_to_725(value:str):
value725 = Web3.to_hex(text=value)
return value725

def query_subgraph(subgraph_url, query):

@enforce_types
def value_from_725(value725) -> str:
value = Web3.to_text(hexstr=value725)
return value


@enforce_types
def info_from_725(info725_list: list) -> Dict[str, str]:
"""
@arguments
info725_list -- eg [{"key":encoded("pair"), "value":encoded("ETH/USDT")},
{"key":encoded("timeframe"), "value":encoded("5m") },
... ]
@return
info_dict -- e.g. {"pair": "ETH/USDT",
"timeframe": "5m",
... }
"""
target_keys = ["pair", "timeframe", "source", "base", "quote"]
info_dict = {}
for key in target_keys:
info_dict[key] = None
for item725 in info725_list:
key725, value725 = item725["key"], item725["value"]
if key725 == key_to_725(key):
value = value_from_725(value725)
info_dict[key] = value
break

return info_dict


@enforce_types
def query_subgraph(subgraph_url:str, query:str) -> Dict[str, dict]:
"""
@arguments
subgraph_url -- e.g. http://172.15.0.15:8000/subgraphs/name/oceanprotocol/ocean-subgraph/graphql
query -- e.g. in docstring above

@return
result -- e.g. {"data" : {"predictContracts": ..}}
"""
request = requests.post(subgraph_url, "", json={"query": query}, timeout=1.5)
if request.status_code != 200:
# pylint: disable=broad-exception-raised
Expand All @@ -13,28 +127,42 @@ def query_subgraph(subgraph_url, query):
result = request.json()
return result


@enforce_types
def get_all_interesting_prediction_contracts(
subgraph_url, pairs=None, timeframes=None, sources=None, owners=None
):
chunk_size = 1000 # max for subgraph = 1000
offset = 0
contracts = {}
# prepare keys
owners_filter = []
pairs_filter = []
timeframes_filter = []
sources_filter = []
if owners:
owners_filter = [owner.lower() for owner in owners.split(",")]
subgraph_url:str,
pairs:Optional[str]=None,
timeframes:Optional[str]=None,
sources:Optional[str]=None,
owners:Optional[str]=None,
) -> Dict[str, dict]:
"""
@description
Query the chain for prediction contracts, then filter down
according to pairs, timeframes, sources, or owners.

@arguments
subgraph_url -- e.g.
pairs -- E.g. filter to "BTC/USDT,ETH/USDT". If None, allow all
timeframes -- E.g. filter to "5m,15m". If None, allow all
sources -- E.g. filter to "binance,kraken". If None, allow all
owners -- E.g. filter to "0x123,0x124". If None, allow all

@return
contracts -- dict of [contract_id] : contract_info
where contract_info is a dict with fields name, address, symbol, ..
"""
if pairs:
pairs_filter = [pair for pair in pairs.split(",") if pairs]
pairs = pairs.split(",")
if timeframes:
timeframes_filter = [
timeframe for timeframe in timeframes.split(",") if timeframes
]
timeframes = timeframes.split(",")
if sources:
sources_filter = [source for source in sources.split(",") if sources]
sources = sources.split(",")
if owners:
owners = owners.lower().split(",")

chunk_size = 1000 # max for subgraph = 1000
offset = 0
contracts = {}

while True:
query = """
Expand Down Expand Up @@ -67,48 +195,31 @@ def get_all_interesting_prediction_contracts(
offset += chunk_size
try:
result = query_subgraph(subgraph_url, query)
if result["data"]["predictContracts"] == []:
contract_list = result["data"]["predictContracts"]
if not contract_list:
break
for contract in result["data"]["predictContracts"]:
# loop 725 values and get what we need
info = {
"pair": None,
"base": None,
"quote": None,
"source": None,
"timeframe": None,
}
for nftData in contract["token"]["nft"]["nftData"]:
for info_key, info_values in info.items():
if (
nftData["key"]
== Web3.keccak(info_key.encode("utf-8")).hex()
):
info[info_key] = Web3.to_text(hexstr=nftData["value"])
# now do filtering
if (
(
len(owners_filter) > 0
and contract["token"]["nft"]["owner"]["id"] not in owners_filter
)
or (
len(pairs_filter) > 0
and info["pair"]
and info["pair"] not in pairs_filter
)
or (
len(timeframes_filter) > 0
and info["timeframe"]
and info["timeframe"] not in timeframes_filter
)
or (
len(sources_filter) > 0
and info["source"]
and info["source"] not in sources_filter
)
):
for contract in contract_list:
info725 = contract["token"]["nft"]["nftData"]
info = info_from_725(info725) # {"pair": "ETH/USDT", "base":...}

# filter out unwanted
owner_id = contract["token"]["nft"]["owner"]["id"]
if owners and (owner_id not in owners):
continue

pair = info["pair"]
if pair and pairs and (pair not in pairs):
continue

timeframe = info["timeframe"]
if timeframe and timeframes and (timeframe not in timeframes):
continue

source = info["source"]
if source and sources and (source not in sources):
continue

# ok, add this one
contracts[contract["id"]] = {
"name": contract["token"]["name"],
"address": contract["id"],
Expand All @@ -118,7 +229,9 @@ def get_all_interesting_prediction_contracts(
"last_submited_epoch": 0,
}
contracts[contract["id"]].update(info)

except Exception as e:
print(e)
return {}

return contracts
15 changes: 15 additions & 0 deletions pdr_backend/utils/test/test_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pdr_backend.utils.constants import (
ZERO_ADDRESS,
SAPPHIRE_TESTNET_RPC,
SAPPHIRE_TESTNET_CHAINID,
SAPPHIRE_MAINNET_RPC,
SAPPHIRE_MAINNET_CHAINID,
)

def test_constants1():
assert ZERO_ADDRESS[:3] == "0x0"
assert "https://" in SAPPHIRE_TESTNET_RPC
assert isinstance(SAPPHIRE_TESTNET_CHAINID, int)
assert "https://" in SAPPHIRE_MAINNET_RPC
assert isinstance(SAPPHIRE_MAINNET_CHAINID, int)

5 changes: 0 additions & 5 deletions pdr_backend/utils/test/test_subgraph.py

This file was deleted.

Loading