Skip to content

Commit

Permalink
Merge pull request #121 from fetchoracle/fetchrng-support
Browse files Browse the repository at this point in the history
FetchRNG implementation with ETH and PLS support
  • Loading branch information
jensendarren committed May 28, 2024
1 parent 79197bf commit d6d3715
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 38 deletions.
28 changes: 25 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ PERCENTAGE_CHANGE_THRESHOLD="0.005" # percentage difference >= 0.5% triggers a r

#LP_PULSE_NETWORK_URL="https://rpc.v4.testnet.pulsechain.com"

#PLS_CURRENCY_SOURCES="usdc,dai"
#PLS_ADDR_SOURCES="0xb7f1f5a3b79b0664184bb0a5893aa9359615171b,0xa2d510bf42d2b9766db186f44a902228e76ef262"
#PLS_LPS_ORDER="WPLS/USDC,WPLS/tDAI"
### testnet sources
# LP_PULSE_NETWORK_URL="https://rpc.v4.testnet.pulsechain.com"
# PLS_CURRENCY_SOURCES="usdc,dai"
# PLS_ADDR_SOURCES="0xb7f1f5a3b79b0664184bb0a5893aa9359615171b,0xa2d510bf42d2b9766db186f44a902228e76ef262"
# PLS_LPS_ORDER="WPLS/USDC,WPLS/tDAI"

### FetchRNG settings

# Etherscan API key is required in order to report using the included RNG
# This is required for reporting to RNG feeds
# ETHERSCAN_API_KEY=

### Settings for interval based RNG feeds
## Interval time with will be used to calculate the timestamp
# INTERVAL=300

# Start time syncs the interval reporter in order to sync with consuming smart contracts
# START_TIME=1653350400

# For RNG managed feeds, this is the name of the RNG feed
# FETCH_RNG_NAME=test




38 changes: 25 additions & 13 deletions src/telliot_feeds/cli/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Optional
import os

import os
import click
from chained_accounts import find_accounts
from click.core import Context
Expand All @@ -26,6 +27,7 @@
from telliot_feeds.queries.query_catalog import query_catalog
from telliot_feeds.reporters.flashbot import FlashbotsReporter
from telliot_feeds.reporters.rng_interval import RNGReporter
from telliot_feeds.reporters.rng_interval_custom import RNGCustomReporter
from telliot_feeds.reporters.fetch_360 import Fetch360Reporter
from telliot_feeds.reporters.fetch_flex import FetchFlexReporter
from telliot_feeds.utils.cfg import check_endpoint
Expand Down Expand Up @@ -371,6 +373,7 @@ async def report(
continue_reporting_on_validator_unreachable: bool,
) -> None:
"""Report values to Fetch oracle"""

ctx.obj["ACCOUNT_NAME"] = account_str
ctx.obj["SIGNATURE_ACCOUNT_NAME"] = signature_account

Expand Down Expand Up @@ -534,24 +537,33 @@ async def report(
**common_reporter_kwargs,
) # type: ignore
else:
reporter = FetchFlexReporter(**{
**common_reporter_kwargs,
"continue_reporting_on_dispute": continue_reporting_on_dispute,
"price_validation_method": price_validation_method,
"price_validation_consensus": price_validation_consensus,
"continue_reporting_on_validator_unreachable": continue_reporting_on_validator_unreachable,
"use_estimate_fee": use_estimate_fee,
"use_gas_api": use_gas_api,
"force_nonce": force_nonce,
"tx_timeout": tx_timeout,
}) # type: ignore
if getattr(chosen_feed.query, 'is_custom_rng', False):
common_reporter_kwargs["wait_period"] = int(os.getenv('REPORT_INTERVAL', "300"))
reporter = RNGCustomReporter(**{
**common_reporter_kwargs,
}) # type: ignore
else:
reporter = FetchFlexReporter(**{
**common_reporter_kwargs,
"continue_reporting_on_dispute": continue_reporting_on_dispute,
"price_validation_method": price_validation_method,
"price_validation_consensus": price_validation_consensus,
"continue_reporting_on_validator_unreachable": continue_reporting_on_validator_unreachable,
"use_estimate_fee": use_estimate_fee,
"use_gas_api": use_gas_api,
"force_nonce": force_nonce,
"tx_timeout": tx_timeout,
}) # type: ignore

os.environ["PRICE_VALIDATION_METHOD"] = price_validation_method

if submit_once:

if chosen_feed.query.asset == 'validated-feed':
await reporter.managed_feed_report(submit_once=True)
else:
_, _ = await reporter.report_once()
return

_, _ = await reporter.report_once()

else:
await reporter.report()
4 changes: 3 additions & 1 deletion src/telliot_feeds/feeds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from telliot_feeds.feeds.sushi_usd_feed import sushi_usd_median_feed
from telliot_feeds.feeds.fetch_rng_feed import fetch_rng_feed
from telliot_feeds.feeds.fetch_rng_manual_feed import fetch_rng_manual_feed
from telliot_feeds.feeds.fetch_rng_custom_feed import fetch_rng_custom_feed
from telliot_feeds.feeds.fetch_usd_feed import fetch_usd_median_feed
from telliot_feeds.feeds.twap_manual_feed import twap_30d_example_manual_feed
from telliot_feeds.feeds.twap_manual_feed import twap_manual_feed
Expand All @@ -66,7 +67,6 @@
from telliot_feeds.feeds.xdai_usd_feed import xdai_usd_median_feed
from telliot_feeds.feeds.yfi_usd_feed import yfi_usd_median_feed


CATALOG_FEEDS = {
"ampleforth-custom": ampl_usd_vwap_feed,
"ampleforth-uspce": uspce_feed,
Expand All @@ -89,6 +89,7 @@
"diva-protocol-example": diva_manual_feed,
"string-query-example": string_query_feed,
"fetch-rng-example": fetch_rng_feed,
"fetch-rng-custom": fetch_rng_custom_feed,
"twap-eth-usd-example": twap_30d_example_manual_feed,
"pls-usd-spot": pls_usd_feed,
"pls-usd-spot-twap": pls_usd_twap_feed,
Expand Down Expand Up @@ -135,6 +136,7 @@
"TWAP": twap_manual_feed,
"DailyVolatility": daily_volatility_manual_feed,
"FetchRNG": fetch_rng_feed,
"FetchRNGCustom": fetch_rng_custom_feed,
"FetchRNGManualResponse": fetch_rng_manual_feed,
"AmpleforthCustomSpotPrice": ampl_usd_vwap_feed,
"AmpleforthUSPCE": uspce_feed,
Expand Down
26 changes: 26 additions & 0 deletions src/telliot_feeds/feeds/fetch_rng_custom_feed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Datafeed for pseudorandom number from hashing multiple blockhashes together."""
from typing import Optional

import os
import chained_accounts
from telliot_core.model.endpoints import RPCEndpoint

from telliot_feeds.datafeed import DataFeed
from telliot_feeds.queries.fetch_rng_custom import FetchRNGCustom
from telliot_feeds.sources.custom_blockhash_aggregator import FetchRNGCustomManualSource

FETCH_RNG_NAME = os.getenv('FETCH_RNG_NAME', "custom")
START_TIME = int(os.getenv('START_TIME', 1653350400)) # 2022-5-24 00:00:00 GMT

local_source = FetchRNGCustomManualSource()

fetch_rng_custom_feed = DataFeed(source=local_source, query=FetchRNGCustom(name=FETCH_RNG_NAME, interval=START_TIME))

async def assemble_rng_datafeed(
timestamp: int
) -> Optional[DataFeed[float]]:
"""Assembles a FetchRNG custom datafeed for the given start timestamp."""
local_source.set_timestamp(timestamp)
feed = DataFeed(source=local_source, query=FetchRNGCustom(name=FETCH_RNG_NAME, interval=START_TIME))

return feed
2 changes: 1 addition & 1 deletion src/telliot_feeds/feeds/fetch_rng_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from telliot_feeds.sources.blockhash_aggregator import FetchRNGManualSource

local_source = FetchRNGManualSource()
local_source.set_timestamp(1660567612)

fetch_rng_feed = DataFeed(source=local_source, query=FetchRNG(timestamp=local_source.timestamp))


Expand Down
64 changes: 64 additions & 0 deletions src/telliot_feeds/queries/fetch_rng_custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import logging
from dataclasses import field, dataclass
from hexbytes import HexBytes
from telliot_feeds.dtypes.value_type import ValueType
from telliot_feeds.queries.abi_query import AbiQuery

from eth_abi import decode_abi
from eth_abi import encode_abi

logger = logging.getLogger(__name__)

@dataclass
class FetchRNGCustomReturnType(ValueType):

abi_type: str = "(bytes32,ufixed256x18)"

def encode(self, value: tuple) -> bytes:
"""An encoder for Fetch RNG response type
Encodes a tuple of float values.
"""
if len(value) != 2 or not isinstance(value[0], HexBytes) or not isinstance(value[1], int):
raise ValueError("Invalid response type")

return encode_abi(["bytes32", "uint256"], value)

def decode(self, bytes_val: bytes) -> tuple:
"""A decoder for for Fetch RNG response type
Decodes a tuple of float values.
"""
return decode_abi(["bytes32", "uint256"], bytes_val)

@dataclass
class FetchRNGCustom(AbiQuery):
"""Returns a pseudorandom number generated from hashing together blockhashes from multiple chains.
Attributes:
name:
name of the custom rng feed
timestamp:
time at which to take the most recent blockhashes (example: 1647624359)
"""
is_custom_rng: bool = field(default=True, init=False)
is_managed_feed: bool = field(default=True, init=False)

name: str
interval: int

#: ABI used for encoding/decoding parameters
abi = [
{"name": "name", "type": "string"},
{"name": "interval", "type": "uint256"}
]

@property
def value_type(self) -> ValueType:
"""Data type returned for a FetchRNG query.
- `bytes32`: 32 bytes hexadecimal value
- `packed`: false
"""
#return ValueType(abi_type="bytes32", packed=False)
return FetchRNGCustomReturnType()
12 changes: 12 additions & 0 deletions src/telliot_feeds/queries/query_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
from telliot_feeds.queries.snapshot import Snapshot
from telliot_feeds.queries.string_query import StringQuery
from telliot_feeds.queries.fetch_rng import FetchRNG
from telliot_feeds.queries.fetch_rng_custom import FetchRNGCustom
import os

START_TIME = int(os.getenv('START_TIME', "1653350400")) # 2022-5-24 00:00:00 GMT
FETCH_RNG_NAME = os.getenv('FETCH_RNG_NAME', "custom") #default feed name to custom

"""Main instance of the Query Catalog."""
query_catalog = Catalog()
Expand Down Expand Up @@ -324,3 +329,10 @@
q=MimicryCollectionStat(
collectionAddress="0x5180db8F5c931aaE63c74266b211F580155ecac8", chainId=1, metric=0),
)

query_catalog.add_entry(
tag="fetch-rng-custom",
title="Fetch RNG Custom",
q=FetchRNGCustom(
name=FETCH_RNG_NAME, interval=START_TIME)
)
7 changes: 6 additions & 1 deletion src/telliot_feeds/reporters/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,12 @@ async def report(self, report_count: Optional[int] = None) -> None:
"""Submit values to Fetch oracles on an interval."""

datafeed = await self.fetch_datafeed()
asset = datafeed.query.asset

try:
asset = getattr(datafeed.query, 'asset', '')
except:
asset = ''
logger.warning("Could not fetch datafeed")

if asset == "validated-feed":
await self.managed_feed_report()
Expand Down
16 changes: 8 additions & 8 deletions src/telliot_feeds/reporters/rng_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
import calendar
import time
import os
from typing import Any
from typing import Optional

Expand All @@ -13,15 +14,14 @@
from telliot_feeds.feeds.fetch_rng_feed import assemble_rng_datafeed
from telliot_feeds.queries.fetch_rng import FetchRNG
from telliot_feeds.reporters.reporter_autopay_utils import get_feed_tip
from telliot_feeds.reporters.fetch_360 import Fetch360Reporter
from telliot_feeds.reporters.fetch_flex import FetchFlexReporter
from telliot_feeds.utils.log import get_logger


logger = get_logger(__name__)

INTERVAL = 60 * 30 # 30 minutes
START_TIME = 1653350400 # 2022-5-24 00:00:00 GMT

INTERVAL = os.getenv('REPORT_INTERVAL', 60 * 5) # 5 minutes
START_TIME = os.getenv('START_TIME', 1653350400) # 2022-5-24 00:00:00 GMT

def get_next_timestamp() -> int:
"""get next target timestamp"""
Expand All @@ -30,9 +30,9 @@ def get_next_timestamp() -> int:
return target_ts


class RNGReporter(Fetch360Reporter):
class RNGReporter(FetchFlexReporter):
"""Reports FetchRNG values at a fixed interval to FetchFlex
on Polygon."""
on Pulsechain."""

async def fetch_datafeed(self) -> Optional[DataFeed[Any]]:
status = ResponseStatus()
Expand All @@ -53,7 +53,7 @@ async def fetch_datafeed(self) -> Optional[DataFeed[Any]]:
logger.info(status.error)
return None

datafeed = await assemble_rng_datafeed(timestamp=rng_timestamp, node=self.endpoint, account=self.account)
datafeed = await assemble_rng_datafeed(timestamp=rng_timestamp)
if datafeed is None:
msg = "Unable to assemble RNG datafeed"
error_status(note=msg, log=logger.warning)
Expand All @@ -77,4 +77,4 @@ async def fetch_datafeed(self) -> Optional[DataFeed[Any]]:
return None
tip += feed_tip
logger.debug(f"Current tip for RNG query: {tip}")
return datafeed
return datafeed
Loading

0 comments on commit d6d3715

Please sign in to comment.