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

Skip manual feeds when listening for tips #745

Merged
merged 5 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/telliot_feeds/cli/commands/conditional.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ async def conditional(
stale_timeout: int,
query_tag: str,
unsafe: bool,
skip_manual_feeds: bool,
) -> None:
"""Report values to Tellor oracle if certain conditions are met."""
click.echo("Starting Conditional Reporter...")
Expand Down Expand Up @@ -137,6 +138,7 @@ async def conditional(
"check_rewards": check_rewards,
"gas_multiplier": gas_multiplier,
"max_priority_fee_range": max_priority_fee_range,
"skip_manual_feeds": skip_manual_feeds,
}

reporter = ConditionalReporter(
Expand Down
2 changes: 2 additions & 0 deletions src/telliot_feeds/cli/commands/liquity.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ async def liquity(
query_tag: str,
chainlink_feed: str,
unsafe: bool,
skip_manual_feeds: bool,
) -> None:
"""Report values to Tellor oracle if certain conditions are met."""
click.echo("Starting Liquity Backup Reporter...")
Expand Down Expand Up @@ -140,6 +141,7 @@ async def liquity(
"check_rewards": check_rewards,
"gas_multiplier": gas_multiplier,
"max_priority_fee_range": max_priority_fee_range,
"skip_manual_feeds": skip_manual_feeds,
}

reporter = ChainlinkBackupReporter(
Expand Down
2 changes: 2 additions & 0 deletions src/telliot_feeds/cli/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ async def report(
max_priority_fee_range: int,
ignore_tbr: bool,
unsafe: bool,
skip_manual_feeds: bool,
) -> None:
"""Report values to Tellor oracle"""
ctx.obj["ACCOUNT_NAME"] = account_str
Expand Down Expand Up @@ -320,6 +321,7 @@ async def report(
"gas_multiplier": gas_multiplier,
"max_priority_fee_range": max_priority_fee_range,
"ignore_tbr": ignore_tbr,
"skip_manual_feeds": skip_manual_feeds,
}
reporter: Union[FlashbotsReporter, RNGReporter, Tellor360Reporter]
if sig_acct_addr:
Expand Down
7 changes: 7 additions & 0 deletions src/telliot_feeds/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ def common_reporter_options(f: Callable[..., Any]) -> Callable[..., Any]:
callback=parse_profit_input,
default="100.0",
)
@click.option(
"--skip-manual-feeds",
help="skip feeds that require manual value input when listening to tips",
nargs=1,
type=bool,
default=True,
)
@functools.wraps(f)
def wrapper(*args: Any, **kwargs: Any) -> Any:
return f(*args, **kwargs)
Expand Down
13 changes: 13 additions & 0 deletions src/telliot_feeds/feeds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,16 @@
"BTCBalance": btc_balance_feed,
"EVMBalance": evm_balance_feed,
}

# populate list with feeds that require manual input
MANUAL_FEEDS: list[str] = [
"SpotPrice",
"DivaProtocol",
"SnapshotOracle",
"NumericApiManualResponse",
"TWAP",
"DailyVolatility",
"TellorRNGManualResponse",
"CustomPrice",
"FileCID",
]
4 changes: 3 additions & 1 deletion src/telliot_feeds/reporters/tellor_360.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ def __init__(
ignore_tbr: bool = False, # relevant only for eth-mainnet and eth-testnets
stake: float = 0,
use_random_feeds: bool = False,
skip_manual_feeds: bool = False,
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
self.autopay = autopay
self.datafeed = datafeed
self.skip_manual_feeds = skip_manual_feeds
self.use_random_feeds: bool = use_random_feeds
self.qtag_selected = False if self.datafeed is None else True
self.expected_profit = expected_profit
Expand Down Expand Up @@ -231,7 +233,7 @@ async def fetch_datafeed(self) -> Optional[DataFeed[Any]]:

# Fetch datafeed based on whichever is most funded in the AutoPay contract
if self.datafeed is None:
suggested_feed, tip_amount = await get_feed_and_tip(self.autopay)
suggested_feed, tip_amount = await get_feed_and_tip(self.autopay, self.skip_manual_feeds)

if suggested_feed is not None and tip_amount is not None:
logger.info(f"Most funded datafeed in Autopay: {suggested_feed.query.type}")
Expand Down
1 change: 1 addition & 0 deletions src/telliot_feeds/reporters/tips/listener/funded_feeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async def get_funded_feed_queries(self) -> tuple[Optional[list[QueryIdandFeedDet
return None, error_status(note="No funded feeds returned by autopay function call")

# List of feeds with telliot supported query types
# TODO: make skipping manual feeds optional
supported_funded_feeds = [
(feed, query_data) for (feed, query_data) in funded_feeds if feed_in_feed_builder_mapping(query_data)
]
Expand Down
7 changes: 5 additions & 2 deletions src/telliot_feeds/reporters/tips/suggest_datafeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# suggest a feed here not a query tag, because build feed portion
# or check both mappings for type
async def get_feed_and_tip(
autopay: TellorFlexAutopayContract, current_timestamp: Optional[TimeStamp] = None
autopay: TellorFlexAutopayContract, skip_manual_feeds: bool, current_timestamp: Optional[TimeStamp] = None
) -> Optional[Tuple[Optional[DataFeed[Any]], Optional[int]]]:
"""Fetch feeds with their tip and filter to get a feed suggestion with the max tip

Expand Down Expand Up @@ -56,7 +56,8 @@ async def get_feed_and_tip(
datafeed = feed_from_catalog_feeds(query_data)

if datafeed is None:
datafeed = feed_in_feed_builder_mapping(query_data)
# TODO: add skip manual feed flag to make optional; currently skips all manual feeds
datafeed = feed_in_feed_builder_mapping(query_data, skip_manual_feeds=skip_manual_feeds)

if datafeed is not None:
query = get_query_from_qtyp_name(query_data)
Expand All @@ -66,4 +67,6 @@ async def get_feed_and_tip(
setattr(datafeed.source, param, val)
break

if datafeed is None:
return None, None
return datafeed, tip_amount
11 changes: 10 additions & 1 deletion src/telliot_feeds/utils/query_search_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
from telliot_feeds.feeds import CATALOG_FEEDS
from telliot_feeds.feeds import DataFeed
from telliot_feeds.feeds import DATAFEED_BUILDER_MAPPING
from telliot_feeds.feeds import MANUAL_FEEDS
from telliot_feeds.queries.query import OracleQuery
from telliot_feeds.queries.query_catalog import query_catalog
from telliot_feeds.utils.log import get_logger

logger = get_logger(__name__)


def decode_typ_name(qdata: bytes) -> str:
Expand Down Expand Up @@ -56,7 +60,7 @@ def feed_from_catalog_feeds(qdata: bytes) -> Optional[DataFeed[Any]]:
return CATALOG_FEEDS.get(qtag) if qtag else None


def feed_in_feed_builder_mapping(qdata: bytes) -> Optional[DataFeed[Any]]:
def feed_in_feed_builder_mapping(qdata: bytes, skip_manual_feeds: bool = False) -> Optional[DataFeed[Any]]:
"""Get feed if query type in DATAFEED_BUILDER_MAPPING

Args:
Expand All @@ -65,6 +69,11 @@ def feed_in_feed_builder_mapping(qdata: bytes) -> Optional[DataFeed[Any]]:
Return: DataFeed
"""
qtyp_name = decode_typ_name(qdata)
if skip_manual_feeds:
if qtyp_name in MANUAL_FEEDS:
logger.info(f"There is a tip for this query type: {qtyp_name}. Query data: {qdata.hex()}, (manual feed)")
return None

return DATAFEED_BUILDER_MAPPING.get(qtyp_name)


Expand Down
49 changes: 39 additions & 10 deletions tests/tips/test_feed_suggestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
async def test_no_tips(autopay_contract_setup, caplog):
"""Test no tips in autopay"""
flex = await autopay_contract_setup
await get_feed_and_tip(flex.autopay)
await get_feed_and_tip(flex.autopay, skip_manual_feeds=False)
assert "No one time tip funded queries available" in caplog.text
assert "No funded feeds returned by autopay function call" in caplog.text
assert "No tips available in autopay" in caplog.text
Expand All @@ -31,7 +31,7 @@ async def test_no_tips(autopay_contract_setup, caplog):
async def test_funded_feeds_only(setup_datafeed, caplog):
"""Test feed tips but no one time tips and no reported timestamps"""
flex = await setup_datafeed
datafeed, tip = await get_feed_and_tip(flex.autopay)
datafeed, tip = await get_feed_and_tip(flex.autopay, skip_manual_feeds=False)
assert isinstance(datafeed, DataFeed)
assert isinstance(tip, int)
assert tip == int(1e18)
Expand All @@ -42,7 +42,7 @@ async def test_funded_feeds_only(setup_datafeed, caplog):
async def test_one_time_tips_only(setup_one_time_tips, caplog):
"""Test one time tips but no feed tips"""
flex = await setup_one_time_tips
datafeed, tip = await get_feed_and_tip(flex.autopay)
datafeed, tip = await get_feed_and_tip(flex.autopay, skip_manual_feeds=False)
assert isinstance(datafeed, DataFeed)
assert isinstance(tip, int)
assert "No funded feeds returned by autopay function call" in caplog.text
Expand All @@ -54,7 +54,7 @@ async def test_fetching_tips(tip_feeds_and_one_time_tips):
A one time tip of 24 TRB exists autopay and plus 1 TRB in a feed
its the highest so it should be the suggested query"""
flex = await tip_feeds_and_one_time_tips
datafeed, tip = await get_feed_and_tip(flex.autopay)
datafeed, tip = await get_feed_and_tip(flex.autopay, skip_manual_feeds=False)
assert isinstance(datafeed, DataFeed)
assert isinstance(tip, int)
assert tip == len(query_catalog._entries) * int(1e18)
Expand Down Expand Up @@ -82,7 +82,7 @@ async def test_fake_queryid_feed_setup(autopay_contract_setup, caplog):
_amount=int(1 * 10**18),
)
assert status.ok
datafeed, tip = await get_feed_and_tip(flex.autopay)
datafeed, tip = await get_feed_and_tip(flex.autopay, skip_manual_feeds=False)
assert datafeed is None
assert tip is None
assert "No funded feeds with telliot support found in autopay" in caplog.text
Expand Down Expand Up @@ -163,7 +163,8 @@ async def test_feed_with_manual_source(autopay_contract_setup, caplog):
_amount=int(10e18),
)
assert status.ok
datafeed, tip = await get_feed_and_tip(r.autopay)

datafeed, tip = await get_feed_and_tip(r.autopay, skip_manual_feeds=False)
assert datafeed.query.asset == "fake"
assert datafeed.query.currency == "usd"
assert datafeed.query.type == "SpotPrice"
Expand All @@ -181,7 +182,7 @@ async def test_feed_with_manual_source(autopay_contract_setup, caplog):
legacy_gas_price=1,
)
chain.mine(timedelta=1)
datafeed, tip = await get_feed_and_tip(r.autopay, current_timestamp=chain.time())
datafeed, tip = await get_feed_and_tip(r.autopay, skip_manual_feeds=False, current_timestamp=chain.time())
assert datafeed is None
assert tip is None
assert "No auto source for feed with query type SpotPrice to check threshold" in caplog.text
Expand Down Expand Up @@ -210,7 +211,7 @@ async def test_no_value_before(autopay_contract_setup, caplog):
# bypass window to force threshold check
chain.mine(timedelta=1)
# check that no feed is suggested since there is no value before and not in window
feed, tip = await get_feed_and_tip(r.autopay, current_timestamp=chain.time())
feed, tip = await get_feed_and_tip(r.autopay, skip_manual_feeds=False, current_timestamp=chain.time())
assert feed is not None
assert tip is not None
assert " No value before for SpotPrice" in caplog.text
Expand Down Expand Up @@ -241,11 +242,39 @@ async def test_low_gas_limit_error(autopay_contract_setup, caplog):

# imitate a low gas limit caused error
with patch.object(AssembleCall, "gas_limit", 5000):
suggestion = await get_feed_and_tip(r.autopay, current_timestamp=chain.time())
suggestion = await get_feed_and_tip(r.autopay, skip_manual_feeds=False, current_timestamp=chain.time())
assert suggestion == (None, None)
assert "Error getting eligible funded feeds: multicall failed to fetch data: ContractLogicError" in caplog.text

# should be no error when using default gas limit
feed, tip_amount = await get_feed_and_tip(r.autopay, current_timestamp=chain.time())
feed, tip_amount = await get_feed_and_tip(r.autopay, skip_manual_feeds=False, current_timestamp=chain.time())
assert feed is not None
assert tip_amount is not None


@pytest.mark.asyncio
async def test_skip_manual_feed(autopay_contract_setup, caplog):
"""Test feed tips when skip_manual_feeds is True"""
flex = await autopay_contract_setup
jai_usd_query_data = "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000953706f745072696365000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000036a6169000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037573640000000000000000000000000000000000000000000000000000000000" # noqa: E501
jai_usd_query_id = "0x8cad613ed5dbbcb23c028a6656cb051a1adf2954c8aaa4cb834b1e7e45069ca4"
# setup a feed on autopay
_, status = await flex.autopay.write(
"setupDataFeed",
gas_limit=3500000,
legacy_gas_price=1,
_queryId=jai_usd_query_id,
_reward=1,
_startTime=chain.time(),
_interval=21600,
_window=60,
_priceThreshold=1,
_rewardIncreasePerSecond=0,
_queryData=jai_usd_query_data,
_amount=int(1 * 10**18),
)
assert status.ok
datafeed, tip = await get_feed_and_tip(flex.autopay, skip_manual_feeds=True)
assert datafeed is None
assert tip is None
assert f"There is a tip for this query type: SpotPrice. Query data: {jai_usd_query_data[2:]}" in caplog.text
4 changes: 2 additions & 2 deletions tests/tips/test_generic_query_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async def test_mashup_type(autopay_contract_setup, caplog):
)
chain.mine(timedelta=1)
reporting_time = chain.time()
datafeed, tip = await get_feed_and_tip(r.autopay, current_timestamp=reporting_time)
datafeed, tip = await get_feed_and_tip(r.autopay, skip_manual_feeds=False, current_timestamp=reporting_time)
assert datafeed.query.type == "MimicryMacroMarketMashup"
time_diff = reporting_time - start_time
assert tip == 2500000000000000 + (1000000000000000 * time_diff)
Expand Down Expand Up @@ -68,7 +68,7 @@ async def test_collection_type(autopay_contract_setup):
)
chain.mine(timedelta=1)
reporting_time = chain.time()
datafeed, tip = await get_feed_and_tip(r.autopay, current_timestamp=reporting_time)
datafeed, tip = await get_feed_and_tip(r.autopay, skip_manual_feeds=False, current_timestamp=reporting_time)
assert datafeed.query.type == "MimicryCollectionStat"
time_diff = reporting_time - start_time
assert tip == 2500000000000000 + (1000000000000000 * time_diff)
Expand Down
47 changes: 47 additions & 0 deletions tests/tips/test_selected_queryid.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,50 @@ async def test_rng(autopay_contract_setup, mumbai_test_cfg, caplog):
_, status = await reporter.report_once()
assert '{"type":"TellorRNG","timestamp":1665592591}' in caplog.text
assert status.ok


@pytest.mark.asyncio
async def test_skip_manual_feed(autopay_contract_setup, mumbai_test_cfg, caplog):
"""Test skip manual feed"""
flex = await autopay_contract_setup
core = TelliotCore(config=mumbai_test_cfg)
account = core.get_account()
reporter = Tellor360Reporter(
oracle=flex.oracle,
token=flex.token,
autopay=flex.autopay,
endpoint=core.endpoint,
account=account,
chain_id=80001,
transaction_type=0,
expected_profit=100,
ignore_tbr=False,
skip_manual_feeds=True,
)
jai_usd_query_data = "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000953706f745072696365000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000036a6169000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037573640000000000000000000000000000000000000000000000000000000000" # noqa: E501
jai_usd_query_id = "0x8cad613ed5dbbcb23c028a6656cb051a1adf2954c8aaa4cb834b1e7e45069ca4"
await reporter.autopay.write(
"tip",
gas_limit=3500000,
legacy_gas_price=1,
_queryId=jai_usd_query_id,
_queryData=jai_usd_query_data,
_amount=int(1e18),
)
_, status = await reporter.report_once()
assert not status.ok
assert f"There is a tip for this query type: SpotPrice. Query data: {jai_usd_query_data[2:]}" in caplog.text

eth_usd_query_data = "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000953706F745072696365000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003657468000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037573640000000000000000000000000000000000000000000000000000000000" # noqa: E501
eth_usd_query_id = "0x83A7F3D48786AC2667503A61E8C415438ED2922EB86A2906E4EE66D9A2CE4992"
# report for eth_usd should work
await reporter.autopay.write(
"tip",
gas_limit=3500000,
legacy_gas_price=1,
_queryId=eth_usd_query_id,
_queryData=eth_usd_query_data,
_amount=int(1e18),
)
_, status = await reporter.report_once()
assert status.ok
Loading
Loading