From 69ec60e8206ac64bbabc8707d8b62d251d699e54 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 5 Aug 2024 14:36:55 +0200 Subject: [PATCH] Process invalid answers (#333) --- .../markets/omen/data_models.py | 1 + .../markets/omen/omen_contracts.py | 56 ++++++++++++++++--- .../markets/omen/omen_resolving.py | 51 ++++++++++++++++- pyproject.toml | 2 +- tests_integration/markets/omen/test_omen.py | 2 +- 5 files changed, 98 insertions(+), 14 deletions(-) diff --git a/prediction_market_agent_tooling/markets/omen/data_models.py b/prediction_market_agent_tooling/markets/omen/data_models.py index 18629543..95515c3a 100644 --- a/prediction_market_agent_tooling/markets/omen/data_models.py +++ b/prediction_market_agent_tooling/markets/omen/data_models.py @@ -31,6 +31,7 @@ OMEN_TRUE_OUTCOME = "Yes" OMEN_FALSE_OUTCOME = "No" INVALID_ANSWER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +INVALID_ANSWER_HEX_BYTES = HexBytes(INVALID_ANSWER) OMEN_BASE_URL = "https://aiomen.eth.limo" PRESAGIO_BASE_URL = "https://presagio.pages.dev" diff --git a/prediction_market_agent_tooling/markets/omen/omen_contracts.py b/prediction_market_agent_tooling/markets/omen/omen_contracts.py index 6ba1b6ac..3d99c82b 100644 --- a/prediction_market_agent_tooling/markets/omen/omen_contracts.py +++ b/prediction_market_agent_tooling/markets/omen/omen_contracts.py @@ -22,6 +22,9 @@ wei_type, xdai_type, ) +from prediction_market_agent_tooling.markets.omen.data_models import ( + INVALID_ANSWER_HEX_BYTES, +) from prediction_market_agent_tooling.tools.contract import ( ContractDepositableWrapperERC20OnGnosisChain, ContractERC20OnGnosisChain, @@ -562,8 +565,7 @@ def submitAnswer( self, api_keys: APIKeys, question_id: HexBytes, - answer: str, - outcomes: list[str], + answer: HexBytes, bond: Wei, max_previous: Wei | None = None, web3: Web3 | None = None, @@ -573,24 +575,60 @@ def submitAnswer( # same as on Omen website: https://github.com/protofire/omen-exchange/blob/763d9c9d05ebf9edacbc1dbaa561aa5d08813c0f/app/src/services/realitio.ts#L363. max_previous = Wei(0) - # Normalise the answer to lowercase, to match Enum values as [YES, NO] against outcomes as ["Yes", "No"]. - answer = answer.lower() - outcomes = [o.lower() for o in outcomes] - return self.send_with_value( api_keys=api_keys, function_name="submitAnswer", function_params=dict( question_id=question_id, - answer=int_to_hexbytes( - outcomes.index(answer) - ), # Contract's method expects answer index in bytes. + answer=answer, max_previous=max_previous, ), amount_wei=bond, web3=web3, ) + def submit_answer( + self, + api_keys: APIKeys, + question_id: HexBytes, + answer: str, + outcomes: list[str], + bond: Wei, + max_previous: Wei | None = None, + web3: Web3 | None = None, + ) -> TxReceipt: + # Normalise the answer to lowercase, to match Enum values as [YES, NO] against outcomes as ["Yes", "No"]. + answer = answer.lower() + outcomes = [o.lower() for o in outcomes] + + return self.submitAnswer( + api_keys=api_keys, + question_id=question_id, + answer=int_to_hexbytes( + outcomes.index(answer) + ), # Contract's method expects answer index in bytes. + bond=bond, + max_previous=max_previous, + web3=web3, + ) + + def submit_answer_invalid( + self, + api_keys: APIKeys, + question_id: HexBytes, + bond: Wei, + max_previous: Wei | None = None, + web3: Web3 | None = None, + ) -> TxReceipt: + return self.submitAnswer( + api_keys=api_keys, + question_id=question_id, + answer=INVALID_ANSWER_HEX_BYTES, + bond=bond, + max_previous=max_previous, + web3=web3, + ) + def claimWinnings( self, api_keys: APIKeys, diff --git a/prediction_market_agent_tooling/markets/omen/omen_resolving.py b/prediction_market_agent_tooling/markets/omen/omen_resolving.py index d05b9fa3..4e6b5c3d 100644 --- a/prediction_market_agent_tooling/markets/omen/omen_resolving.py +++ b/prediction_market_agent_tooling/markets/omen/omen_resolving.py @@ -1,3 +1,5 @@ +from datetime import datetime + from web3 import Web3 from prediction_market_agent_tooling.config import APIKeys @@ -140,6 +142,7 @@ def claim_bonds_on_realitio_question( def finalize_markets( api_keys: APIKeys, markets_with_resolutions: list[tuple[OmenMarket, Resolution | None]], + wait_n_days_before_invalid: int = 30, web3: Web3 | None = None, ) -> list[HexAddress]: finalized_markets: list[HexAddress] = [] @@ -148,9 +151,24 @@ def finalize_markets( logger.info( f"[{idx+1} / {len(markets_with_resolutions)}] Looking into {market.url=} {market.question_title=}" ) + closed_before_days = (datetime.now() - market.close_time).days if resolution is None: - logger.warning(f"No resolution provided for {market.url=}") + if closed_before_days > wait_n_days_before_invalid: + logger.warning( + f"Finalizing as invalid, market closed before {closed_before_days} days: {market.url=}" + ) + omen_submit_invalid_answer_market_tx( + api_keys, + market, + OMEN_DEFAULT_REALITIO_BOND_VALUE, + web3=web3, + ) + + else: + logger.warning( + f"Skipping, no resolution provided, market closed before {closed_before_days} days: {market.url=}" + ) elif resolution in (Resolution.YES, Resolution.NO): logger.info(f"Found resolution {resolution.value=} for {market.url=}") @@ -165,7 +183,15 @@ def finalize_markets( logger.info(f"Finalized {market.url=}") else: - logger.error(f"Invalid resolution found, {resolution=}, for {market.url=}") + logger.warning( + f"Invalid resolution found, {resolution=}, for {market.url=}, finalizing as invalid." + ) + omen_submit_invalid_answer_market_tx( + api_keys, + market, + OMEN_DEFAULT_REALITIO_BOND_VALUE, + web3=web3, + ) return finalized_markets @@ -199,7 +225,7 @@ def omen_submit_answer_market_tx( And after the period is over, you need to resolve the market using `omen_resolve_market_tx`. """ realitio_contract = OmenRealitioContract() - realitio_contract.submitAnswer( + realitio_contract.submit_answer( api_keys=api_keys, question_id=market.question.id, answer=resolution.value, @@ -209,6 +235,25 @@ def omen_submit_answer_market_tx( ) +def omen_submit_invalid_answer_market_tx( + api_keys: APIKeys, + market: OmenMarket, + bond: xDai, + web3: Web3 | None = None, +) -> None: + """ + After the answer is submitted, there is 24h waiting period where the answer can be challenged by others. + And after the period is over, you need to resolve the market using `omen_resolve_market_tx`. + """ + realitio_contract = OmenRealitioContract() + realitio_contract.submit_answer_invalid( + api_keys=api_keys, + question_id=market.question.id, + bond=xdai_to_wei(bond), + web3=web3, + ) + + def omen_resolve_market_tx( api_keys: APIKeys, market: OmenMarket, diff --git a/pyproject.toml b/pyproject.toml index 885df873..bfaedabc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "prediction-market-agent-tooling" -version = "0.45.1" +version = "0.46.0" description = "Tools to benchmark, deploy and monitor prediction market agents." authors = ["Gnosis"] readme = "README.md" diff --git a/tests_integration/markets/omen/test_omen.py b/tests_integration/markets/omen/test_omen.py index 61c53583..0c67a624 100644 --- a/tests_integration/markets/omen/test_omen.py +++ b/tests_integration/markets/omen/test_omen.py @@ -100,7 +100,7 @@ def test_create_bet_withdraw_resolve_market( # Submit the answer and verify it was successfully submitted. logger.debug(f"Submitting the answer to {market.question.id=}.") - OmenRealitioContract().submitAnswer( + OmenRealitioContract().submit_answer( api_keys=test_keys, question_id=market.question.id, answer=OMEN_FALSE_OUTCOME,