From 4617cbbb6f4503f16ad64f75927359faa36a5c37 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:22:01 +0000 Subject: [PATCH 01/17] Add filter for paused contracts in subgraph queries --- pdr_backend/analytics/check_network.py | 2 +- pdr_backend/subgraph/legacy/subgraph_slot.py | 1 + pdr_backend/subgraph/subgraph_consume_so_far.py | 2 +- pdr_backend/subgraph/subgraph_dfbuyer.py | 2 +- pdr_backend/subgraph/subgraph_feed_contracts.py | 2 +- pdr_backend/subgraph/subgraph_payout.py | 3 ++- pdr_backend/subgraph/subgraph_pending_payouts.py | 2 +- pdr_backend/subgraph/subgraph_pending_slots.py | 2 +- pdr_backend/subgraph/subgraph_predictions.py | 3 ++- pdr_backend/subgraph/subgraph_slot.py | 1 + pdr_backend/subgraph/subgraph_subscriptions.py | 4 ++-- pdr_backend/subgraph/subgraph_sync.py | 2 +- pdr_backend/subgraph/subgraph_trueval.py | 2 +- 13 files changed, 16 insertions(+), 12 deletions(-) diff --git a/pdr_backend/analytics/check_network.py b/pdr_backend/analytics/check_network.py index d1fe3ac6f..c2264b277 100644 --- a/pdr_backend/analytics/check_network.py +++ b/pdr_backend/analytics/check_network.py @@ -114,7 +114,7 @@ def do_query_network(subgraph_url: str, lookback_hours: int): start_ut = cur_ut - lookback_hours * 60 * 60 query = """ { - predictContracts{ + predictContracts(where: {paused: false}){ id token{ name diff --git a/pdr_backend/subgraph/legacy/subgraph_slot.py b/pdr_backend/subgraph/legacy/subgraph_slot.py index eba543cc8..902874a55 100644 --- a/pdr_backend/subgraph/legacy/subgraph_slot.py +++ b/pdr_backend/subgraph/legacy/subgraph_slot.py @@ -58,6 +58,7 @@ def get_predict_slots_query( slot_lte: {initial_slot} slot_gte: {last_slot} predictContract_in: {asset_ids_str} + predictContract_: {{paused: false}} }} ) {{ id diff --git a/pdr_backend/subgraph/subgraph_consume_so_far.py b/pdr_backend/subgraph/subgraph_consume_so_far.py index 14a2cab4a..7ce98f861 100644 --- a/pdr_backend/subgraph/subgraph_consume_so_far.py +++ b/pdr_backend/subgraph/subgraph_consume_so_far.py @@ -24,7 +24,7 @@ def get_consume_so_far_per_contract( while True: query = """ { - predictSubscriptions(where: {timestamp_gt:%s, user_:{id: "%s"}}, first: %s, skip: %s){ + predictSubscriptions(where: {timestamp_gt:%s, user_:{id: "%s"}, predictContract_: {paused: false}}, first: %s, skip: %s){ id timestamp user { diff --git a/pdr_backend/subgraph/subgraph_dfbuyer.py b/pdr_backend/subgraph/subgraph_dfbuyer.py index 963f08a42..9000be00b 100644 --- a/pdr_backend/subgraph/subgraph_dfbuyer.py +++ b/pdr_backend/subgraph/subgraph_dfbuyer.py @@ -17,7 +17,7 @@ def get_consume_so_far( while True: # pylint: disable=too-many-nested-blocks query = """ { - predictContracts(skip:%s, first:%s){ + predictContracts(where: {paused: false}, skip:%s, first:%s){ id token{ orders(where: {createdTimestamp_gt:%s, consumer_in:["%s"]}){ diff --git a/pdr_backend/subgraph/subgraph_feed_contracts.py b/pdr_backend/subgraph/subgraph_feed_contracts.py index eff4ee9a6..8a5f47b80 100644 --- a/pdr_backend/subgraph/subgraph_feed_contracts.py +++ b/pdr_backend/subgraph/subgraph_feed_contracts.py @@ -40,7 +40,7 @@ def query_feed_contracts( while True: query = """ { - predictContracts(skip:%s, first:%s){ + predictContracts(where: {paused: false}, skip:%s, first:%s){ id token { id diff --git a/pdr_backend/subgraph/subgraph_payout.py b/pdr_backend/subgraph/subgraph_payout.py index 9b2f5f75a..a626a12bb 100644 --- a/pdr_backend/subgraph/subgraph_payout.py +++ b/pdr_backend/subgraph/subgraph_payout.py @@ -41,7 +41,8 @@ def get_payout_query( { timestamp_gte: %s, timestamp_lte: %s, - prediction_contains: "%s" + prediction_contains: "%s", + prediction_: {slot_: {predictContract_: {paused: false}}} } """ % (start_ts, end_ts, asset_id) diff --git a/pdr_backend/subgraph/subgraph_pending_payouts.py b/pdr_backend/subgraph/subgraph_pending_payouts.py index f99e06936..8ed6869c3 100644 --- a/pdr_backend/subgraph/subgraph_pending_payouts.py +++ b/pdr_backend/subgraph/subgraph_pending_payouts.py @@ -24,7 +24,7 @@ def _fetch_subgraph_payouts( query = """ { predictPredictions( - where: { user: "%s", payout: null, slot_: { %s } }, + where: { user: "%s", payout: null, slot_: { %s, predictContract_: {paused: false} } }, first: %d, skip: %d ) { diff --git a/pdr_backend/subgraph/subgraph_pending_slots.py b/pdr_backend/subgraph/subgraph_pending_slots.py index bf91c78cc..f3f9d4f9d 100644 --- a/pdr_backend/subgraph/subgraph_pending_slots.py +++ b/pdr_backend/subgraph/subgraph_pending_slots.py @@ -33,7 +33,7 @@ def get_pending_slots( while True: query = """ { - predictSlots(where: {slot_gt: %s, slot_lte: %s, status: "Pending"}, skip:%s, first:%s){ + predictSlots(where: {slot_gt: %s, slot_lte: %s, status: "Pending", predictContract_: {paused: false}}, skip:%s, first:%s){ id slot status diff --git a/pdr_backend/subgraph/subgraph_predictions.py b/pdr_backend/subgraph/subgraph_predictions.py index c5ec0d3a0..be74b54ef 100644 --- a/pdr_backend/subgraph/subgraph_predictions.py +++ b/pdr_backend/subgraph/subgraph_predictions.py @@ -71,7 +71,7 @@ def fetch_filtered_predictions( filters = [f.lower() for f in addresses] # pylint: disable=line-too-long - where_clause = f", where: {{timestamp_gt: {start_ts}, timestamp_lt: {end_ts}, slot_: {{predictContract_in: {json.dumps(filters)}}}}}" + where_clause = f", where: {{timestamp_gt: {start_ts}, timestamp_lt: {end_ts}, slot_: {{predictContract_in: {json.dumps(filters)}, predictContract_: {{paused: false}}}}}}" query = f""" {{ @@ -242,6 +242,7 @@ def fetch_contract_id_and_spe( { predictContracts(where: { id_in: %s + paused: false }){ id secondsPerEpoch diff --git a/pdr_backend/subgraph/subgraph_slot.py b/pdr_backend/subgraph/subgraph_slot.py index d085baa61..198b21306 100644 --- a/pdr_backend/subgraph/subgraph_slot.py +++ b/pdr_backend/subgraph/subgraph_slot.py @@ -41,6 +41,7 @@ def get_predict_slots_query( slot_lte: %s slot_gte: %s predictContract_in: %s + predictContract_: {paused: false} }, orderBy: slot, orderDirection: asc diff --git a/pdr_backend/subgraph/subgraph_subscriptions.py b/pdr_backend/subgraph/subgraph_subscriptions.py index 24d1a1893..1eeb7140d 100644 --- a/pdr_backend/subgraph/subgraph_subscriptions.py +++ b/pdr_backend/subgraph/subgraph_subscriptions.py @@ -51,9 +51,9 @@ def fetch_filtered_subscriptions( # pylint: disable=line-too-long if len(contracts) > 0: - where_clause = f", where: {{predictContract_: {{id_in: {json.dumps(contracts)}}}, timestamp_gt: {start_ts}, timestamp_lt: {end_ts}}}" + where_clause = f", where: {{predictContract_: {{id_in: {json.dumps(contracts)}, paused: false}}, timestamp_gt: {start_ts}, timestamp_lt: {end_ts}}}" else: - where_clause = f", where: {{timestamp_gt: {start_ts}, timestamp_lt: {end_ts}}}" + where_clause = f", where: {{timestamp_gt: {start_ts}, timestamp_lt: {end_ts}, predictContract_: {{paused: false}}}}" # pylint: disable=line-too-long query = f""" diff --git a/pdr_backend/subgraph/subgraph_sync.py b/pdr_backend/subgraph/subgraph_sync.py index 76bfd7f75..ffacf29a6 100644 --- a/pdr_backend/subgraph/subgraph_sync.py +++ b/pdr_backend/subgraph/subgraph_sync.py @@ -13,7 +13,7 @@ def block_number_is_synced(subgraph_url: str, block_number: int) -> bool: query = """ { - predictContracts(block:{number:%s}){ + predictContracts(block:{number:%s}, where: {paused: false}){ id } } diff --git a/pdr_backend/subgraph/subgraph_trueval.py b/pdr_backend/subgraph/subgraph_trueval.py index ab6eb5939..94572c08a 100644 --- a/pdr_backend/subgraph/subgraph_trueval.py +++ b/pdr_backend/subgraph/subgraph_trueval.py @@ -40,7 +40,7 @@ def get_truevals_query( predictTrueVals ( first: %s skip: %s - where: { timestamp_gte: %s, timestamp_lte: %s, slot_: {predictContract_in: %s}}, + where: { timestamp_gte: %s, timestamp_lte: %s, slot_: {predictContract_in: %s, predictContract_: {paused: false}}}, orderBy: timestamp, orderDirection: asc ) { From 49a317d92034c8adff0977adaf3626bb585d07a7 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:25:34 +0000 Subject: [PATCH 02/17] Add integration test for filtering out paused contracts in subgraph queries --- .../test_paused_contracts_filtered.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 system_tests/test_paused_contracts_filtered.py diff --git a/system_tests/test_paused_contracts_filtered.py b/system_tests/test_paused_contracts_filtered.py new file mode 100644 index 000000000..e2a136cd9 --- /dev/null +++ b/system_tests/test_paused_contracts_filtered.py @@ -0,0 +1,86 @@ +import pytest +from pdr_backend.subgraph.subgraph_feed_contracts import query_feed_contracts +from pdr_backend.subgraph.subgraph_pending_slots import get_pending_slots +from pdr_backend.subgraph.subgraph_predictions import fetch_filtered_predictions +from pdr_backend.util.time_types import UnixTimeS + + +def test_paused_contracts_are_filtered_out( + web3_pp, + predictoor_contract, +): + """ + Integration test that verifies paused contracts are filtered out. + + Steps: + 1. Deploy a predictoor contract (done via fixture) + 2. Query subgraph for contracts - should include our contract + 3. Pause the contract + 4. Wait for subgraph to sync + 5. Query subgraph again - should NOT include our contract + """ + + contract_address = predictoor_contract.contract_instance.address + subgraph_url = web3_pp.subgraph_url + + # Step 1: Verify contract appears in feed contracts query (not paused) + feeds_before = query_feed_contracts(subgraph_url=subgraph_url) + contract_found_before = contract_address.lower() in [ + addr.lower() for addr in feeds_before.keys() + ] + + # If contract is new, it should appear; if already paused, skip test + if not contract_found_before: + pytest.skip("Contract not found in subgraph - may be new or already paused") + + # Step 2: Verify contract appears in pending slots query (not paused) + current_time = UnixTimeS.now() + slots_before = get_pending_slots( + subgraph_url=subgraph_url, + timestamp=current_time, + owner_addresses=None, + allowed_feeds=None, + ) + + # Step 3: Pause the contract + # Note: This requires owner permissions + tx = predictoor_contract.contract_instance.functions.pause().transact( + web3_pp.tx_call_params() + ) + web3_pp.w3.eth.wait_for_transaction_receipt(tx) + + # Verify contract is paused + is_paused = predictoor_contract.contract_instance.functions.paused().call() + assert is_paused, "Contract should be paused" + + # Step 4: Wait for subgraph to sync + from pdr_backend.subgraph.subgraph_sync import wait_until_subgraph_syncs + wait_until_subgraph_syncs(web3_pp, subgraph_url) + + # Step 5: Verify contract does NOT appear in feed contracts query (paused) + feeds_after = query_feed_contracts(subgraph_url=subgraph_url) + contract_found_after = contract_address.lower() in [ + addr.lower() for addr in feeds_after.keys() + ] + + assert not contract_found_after, ( + f"Paused contract {contract_address} should NOT be in feed contracts query results" + ) + + # Step 6: Verify contract does NOT appear in pending slots query (paused) + slots_after = get_pending_slots( + subgraph_url=subgraph_url, + timestamp=current_time, + owner_addresses=None, + allowed_feeds=None, + ) + + slots_with_paused_contract = [ + slot for slot in slots_after + if slot.feed.address.lower() == contract_address.lower() + ] + + assert len(slots_with_paused_contract) == 0, ( + f"Paused contract {contract_address} should NOT have any slots in query results" + ) + \ No newline at end of file From 0561558d7517e826a70eeb4842922daf95ccdc1e Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:26:22 +0000 Subject: [PATCH 03/17] format --- pdr_backend/lake_info/overview.py | 2 +- pdr_backend/util/mathutil.py | 6 +-- .../test_paused_contracts_filtered.py | 43 ++++++++++--------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/pdr_backend/lake_info/overview.py b/pdr_backend/lake_info/overview.py index 7c0f08af4..b919b08ed 100644 --- a/pdr_backend/lake_info/overview.py +++ b/pdr_backend/lake_info/overview.py @@ -169,7 +169,7 @@ def validate_lake_bronze_predictions_gaps(self) -> List[str]: gap_pct = ( ( counts_per_timedelta.group_by(["pair", "timeframe"]) - .agg([(pl.sum("total_count").alias("sum_total_count"))]) + .agg([pl.sum("total_count").alias("sum_total_count")]) .join( counts_per_timedelta, on=["pair", "timeframe"], diff --git a/pdr_backend/util/mathutil.py b/pdr_backend/util/mathutil.py index d7b9ae545..0af54c266 100644 --- a/pdr_backend/util/mathutil.py +++ b/pdr_backend/util/mathutil.py @@ -15,7 +15,7 @@ def round_sig(x: Union[int, float], sig: int) -> Union[int, float]: @enforce_types def all_nan( - x: Union[np.ndarray, pd.DataFrame, pd.Series, pl.DataFrame, pl.Series] + x: Union[np.ndarray, pd.DataFrame, pd.Series, pl.DataFrame, pl.Series], ) -> bool: """Returns True if all entries in x have a nan _or_ a None""" if isinstance(x, np.ndarray): @@ -47,7 +47,7 @@ def all_nan( @enforce_types def has_nan( - x: Union[np.ndarray, pd.DataFrame, pd.Series, pl.DataFrame, pl.Series] + x: Union[np.ndarray, pd.DataFrame, pd.Series, pl.DataFrame, pl.Series], ) -> bool: """Returns True if any entry in x has a nan _or_ a None""" if isinstance(x, np.ndarray): @@ -68,7 +68,7 @@ def has_nan( @enforce_types def fill_nans( - df: Union[pd.DataFrame, pl.DataFrame] + df: Union[pd.DataFrame, pl.DataFrame], ) -> Union[pd.DataFrame, pl.DataFrame]: """Interpolate the nans using Linear method available in pandas. It ignores the index and treat the values as equally spaced. diff --git a/system_tests/test_paused_contracts_filtered.py b/system_tests/test_paused_contracts_filtered.py index e2a136cd9..dbe42f8fa 100644 --- a/system_tests/test_paused_contracts_filtered.py +++ b/system_tests/test_paused_contracts_filtered.py @@ -11,7 +11,7 @@ def test_paused_contracts_are_filtered_out( ): """ Integration test that verifies paused contracts are filtered out. - + Steps: 1. Deploy a predictoor contract (done via fixture) 2. Query subgraph for contracts - should include our contract @@ -19,20 +19,20 @@ def test_paused_contracts_are_filtered_out( 4. Wait for subgraph to sync 5. Query subgraph again - should NOT include our contract """ - + contract_address = predictoor_contract.contract_instance.address subgraph_url = web3_pp.subgraph_url - + # Step 1: Verify contract appears in feed contracts query (not paused) feeds_before = query_feed_contracts(subgraph_url=subgraph_url) contract_found_before = contract_address.lower() in [ addr.lower() for addr in feeds_before.keys() ] - + # If contract is new, it should appear; if already paused, skip test if not contract_found_before: pytest.skip("Contract not found in subgraph - may be new or already paused") - + # Step 2: Verify contract appears in pending slots query (not paused) current_time = UnixTimeS.now() slots_before = get_pending_slots( @@ -41,32 +41,33 @@ def test_paused_contracts_are_filtered_out( owner_addresses=None, allowed_feeds=None, ) - + # Step 3: Pause the contract # Note: This requires owner permissions tx = predictoor_contract.contract_instance.functions.pause().transact( web3_pp.tx_call_params() ) web3_pp.w3.eth.wait_for_transaction_receipt(tx) - + # Verify contract is paused is_paused = predictoor_contract.contract_instance.functions.paused().call() assert is_paused, "Contract should be paused" - + # Step 4: Wait for subgraph to sync from pdr_backend.subgraph.subgraph_sync import wait_until_subgraph_syncs + wait_until_subgraph_syncs(web3_pp, subgraph_url) - + # Step 5: Verify contract does NOT appear in feed contracts query (paused) feeds_after = query_feed_contracts(subgraph_url=subgraph_url) contract_found_after = contract_address.lower() in [ addr.lower() for addr in feeds_after.keys() ] - - assert not contract_found_after, ( - f"Paused contract {contract_address} should NOT be in feed contracts query results" - ) - + + assert ( + not contract_found_after + ), f"Paused contract {contract_address} should NOT be in feed contracts query results" + # Step 6: Verify contract does NOT appear in pending slots query (paused) slots_after = get_pending_slots( subgraph_url=subgraph_url, @@ -74,13 +75,13 @@ def test_paused_contracts_are_filtered_out( owner_addresses=None, allowed_feeds=None, ) - + slots_with_paused_contract = [ - slot for slot in slots_after + slot + for slot in slots_after if slot.feed.address.lower() == contract_address.lower() ] - - assert len(slots_with_paused_contract) == 0, ( - f"Paused contract {contract_address} should NOT have any slots in query results" - ) - \ No newline at end of file + + assert ( + len(slots_with_paused_contract) == 0 + ), f"Paused contract {contract_address} should NOT have any slots in query results" From a45bb6aa2380de7dc653eeb1d442a82ea7398a68 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:42:28 +0000 Subject: [PATCH 04/17] fix --- system_tests/test_paused_contracts_filtered.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system_tests/test_paused_contracts_filtered.py b/system_tests/test_paused_contracts_filtered.py index dbe42f8fa..f2553d6f8 100644 --- a/system_tests/test_paused_contracts_filtered.py +++ b/system_tests/test_paused_contracts_filtered.py @@ -7,7 +7,7 @@ def test_paused_contracts_are_filtered_out( web3_pp, - predictoor_contract, + feed_contract1, ): """ Integration test that verifies paused contracts are filtered out. @@ -20,7 +20,7 @@ def test_paused_contracts_are_filtered_out( 5. Query subgraph again - should NOT include our contract """ - contract_address = predictoor_contract.contract_instance.address + contract_address = feed_contract1.contract_instance.address subgraph_url = web3_pp.subgraph_url # Step 1: Verify contract appears in feed contracts query (not paused) @@ -44,13 +44,13 @@ def test_paused_contracts_are_filtered_out( # Step 3: Pause the contract # Note: This requires owner permissions - tx = predictoor_contract.contract_instance.functions.pause().transact( + tx = feed_contract1.contract_instance.functions.pause().transact( web3_pp.tx_call_params() ) web3_pp.w3.eth.wait_for_transaction_receipt(tx) # Verify contract is paused - is_paused = predictoor_contract.contract_instance.functions.paused().call() + is_paused = feed_contract1.contract_instance.functions.paused().call() assert is_paused, "Contract should be paused" # Step 4: Wait for subgraph to sync From b61fb6566876b85f6456d9bff29317410b819890 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:24:29 +0000 Subject: [PATCH 05/17] feat: add --include_paused option to claim_payouts command for querying paused contracts --- pdr_backend/cli/cli_arguments.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/pdr_backend/cli/cli_arguments.py b/pdr_backend/cli/cli_arguments.py index 44f82f4c2..9186c6218 100644 --- a/pdr_backend/cli/cli_arguments.py +++ b/pdr_backend/cli/cli_arguments.py @@ -24,7 +24,7 @@ pdr predictoor PPSS_FILE NETWORK pdr dashboard PPSS_FILE NETWORK pdr trader APPROACH PPSS_FILE NETWORK - pdr claim_payouts PPSS_FILE + pdr claim_payouts PPSS_FILE [--include_paused] pdr claim_ROSE PPSS_FILE """ @@ -276,6 +276,17 @@ def add_argument_NATIVE_TOKEN(self): ) +@enforce_types +class INCLUDE_PAUSED_Mixin: + def add_argument_INCLUDE_PAUSED(self): + self.add_argument( + "--include_paused", + action="store_true", + default=False, + help="Include paused contracts when querying for payouts", + ) + + # ======================================================================== # argparser base classes class CustomArgParser(NestedArgParser): @@ -554,7 +565,15 @@ def print_args(arguments: Namespace, nested_args: dict): SimArgParser = _ArgParser_PPSS PredictoorArgParser = _ArgParser_PPSS_NETWORK TraderArgParser = _ArgParser_APPROACH_PPSS_NETWORK -ClaimOceanArgParser = _ArgParser_PPSS + + +@enforce_types +class ClaimOceanArgParser(CustomArgParser, PPSS_Mixin, INCLUDE_PAUSED_Mixin): + def __init__(self, description: str, command_name: str): + super().__init__(description=description) + self.add_arguments_bulk(command_name, ["PPSS", "INCLUDE_PAUSED"]) + + ClaimRoseArgParser = _ArgParser_PPSS # power tools From c893cbc128490dc8fe0703b9ddf5280fe5b277e9 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:24:38 +0000 Subject: [PATCH 06/17] feat: pass include_paused argument to do_ocean_payout in do_claim_payouts function --- pdr_backend/cli/cli_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pdr_backend/cli/cli_module.py b/pdr_backend/cli/cli_module.py index e8202b705..da7ddccb0 100644 --- a/pdr_backend/cli/cli_module.py +++ b/pdr_backend/cli/cli_module.py @@ -129,7 +129,8 @@ def do_claim_payouts(args, nested_args=None): network="sapphire-mainnet", nested_override_args=nested_args, ) - do_ocean_payout(ppss) + include_paused = args.include_paused + do_ocean_payout(ppss, include_paused=include_paused) @enforce_types From 19c456249a7261128806d96a0d531c90cc988522 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:25:01 +0000 Subject: [PATCH 07/17] feat: add include_paused parameter to payout functions and update tests --- pdr_backend/cli/test/test_cli_module.py | 31 ++++++++++++++++++- pdr_backend/payout/payout.py | 12 ++++--- .../subgraph/subgraph_pending_payouts.py | 13 ++++++-- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/pdr_backend/cli/test/test_cli_module.py b/pdr_backend/cli/test/test_cli_module.py index 594812b88..6f1e80766 100644 --- a/pdr_backend/cli/test/test_cli_module.py +++ b/pdr_backend/cli/test/test_cli_module.py @@ -110,6 +110,14 @@ class _NATIVE_TOKEN: NATIVE_TOKEN = True +class _INCLUDE_PAUSED: + include_paused = False + + +class _INCLUDE_PAUSED_TRUE: + include_paused = True + + class _Base: def __init__(self, *args, **kwargs): pass @@ -117,7 +125,15 @@ def __init__(self, *args, **kwargs): class MockArgParser_PPSS(_Base): def parse_args(self): - class MockArgs(Namespace, _PPSS, _PPSS_OBJ): + class MockArgs(Namespace, _PPSS, _PPSS_OBJ, _INCLUDE_PAUSED): + pass + + return MockArgs() + + +class MockArgParser_PPSS_WITH_PAUSED(_Base): + def parse_args(self): + class MockArgs(Namespace, _PPSS, _PPSS_OBJ, _INCLUDE_PAUSED_TRUE): pass return MockArgs() @@ -265,6 +281,19 @@ def test_do_claim_payouts(monkeypatch): do_claim_payouts(MockArgParser_PPSS().parse_args()) mock_f.assert_called() + # Verify it was called with include_paused=False by default + assert mock_f.call_args[1]["include_paused"] is False + + +@enforce_types +def test_do_claim_payouts_with_include_paused(monkeypatch): + mock_f = Mock() + monkeypatch.setattr(f"{_CLI_PATH}.do_ocean_payout", mock_f) + + do_claim_payouts(MockArgParser_PPSS_WITH_PAUSED().parse_args()) + mock_f.assert_called() + # Verify it was called with include_paused=True + assert mock_f.call_args[1]["include_paused"] is True @enforce_types diff --git a/pdr_backend/payout/payout.py b/pdr_backend/payout/payout.py index dad639ae5..59caeabf0 100644 --- a/pdr_backend/payout/payout.py +++ b/pdr_backend/payout/payout.py @@ -55,7 +55,9 @@ def request_payout_batches( @enforce_types -def find_slots_and_payout_with_mgr(pred_submitter_mgr, ppss, query_old_slots=False): +def find_slots_and_payout_with_mgr( + pred_submitter_mgr, ppss, query_old_slots=False, include_paused=False +): # we only need to query in one direction, since both predict on the same slots # query_old_slots is by default false to improve bot speed, # running the command line argument will set it to true @@ -65,7 +67,9 @@ def find_slots_and_payout_with_mgr(pred_submitter_mgr, ppss, query_old_slots=Fal logger.info("Starting payout") wait_until_subgraph_syncs(web3_config, subgraph_url) logger.info("Finding pending payouts") - pending_slots = query_pending_payouts(subgraph_url, up_addr, query_old_slots) + pending_slots = query_pending_payouts( + subgraph_url, up_addr, query_old_slots, include_paused + ) payout_batch_size = ppss.predictoor_ss.payout_batch_size shared_slots = find_shared_slots(pending_slots, payout_batch_size) unique_slots = count_unique_slots(shared_slots) @@ -88,7 +92,7 @@ def find_slots_and_payout_with_mgr(pred_submitter_mgr, ppss, query_old_slots=Fal @enforce_types -def do_ocean_payout(ppss: PPSS, check_network: bool = True): +def do_ocean_payout(ppss: PPSS, check_network: bool = True, include_paused: bool = False): web3_config = ppss.web3_pp.web3_config if check_network: assert ppss.web3_pp.network == "sapphire-mainnet" @@ -97,7 +101,7 @@ def do_ocean_payout(ppss: PPSS, check_network: bool = True): pred_submitter_mgr_addr = ppss.predictoor_ss.pred_submitter_mgr pred_submitter_mgr = PredSubmitterMgr(ppss.web3_pp, pred_submitter_mgr_addr) - find_slots_and_payout_with_mgr(pred_submitter_mgr, ppss, True) + find_slots_and_payout_with_mgr(pred_submitter_mgr, ppss, True, include_paused) @enforce_types diff --git a/pdr_backend/subgraph/subgraph_pending_payouts.py b/pdr_backend/subgraph/subgraph_pending_payouts.py index 8ed6869c3..0563b0c3b 100644 --- a/pdr_backend/subgraph/subgraph_pending_payouts.py +++ b/pdr_backend/subgraph/subgraph_pending_payouts.py @@ -11,20 +11,24 @@ def _fetch_subgraph_payouts( - subgraph_url: str, addr: str, slot_filter: str, chunk_size: int + subgraph_url: str, addr: str, slot_filter: str, chunk_size: int, include_paused: bool = False ) -> List[Dict[str, Any]]: """ slot_filter: string inside slot_{ ... } e.g. 'status_in: ["Paying","Canceled"]' or 'status_in: ["Paying","Canceled","Pending"], slot_gte: %d, slot_lt: %d' % (a,b) + include_paused: if True, include paused contracts in the query """ results = [] offset = 0 while True: + # Conditionally add the paused filter + paused_filter = "" if include_paused else ", predictContract_: {paused: false}" + query = """ { predictPredictions( - where: { user: "%s", payout: null, slot_: { %s, predictContract_: {paused: false} } }, + where: { user: "%s", payout: null, slot_: { %s%s } }, first: %d, skip: %d ) { @@ -40,6 +44,7 @@ def _fetch_subgraph_payouts( """ % ( addr, slot_filter, + paused_filter, chunk_size, offset, ) @@ -69,7 +74,7 @@ def _fetch_subgraph_payouts( @enforce_types def query_pending_payouts( - subgraph_url: str, addr: str, query_old_slots=False + subgraph_url: str, addr: str, query_old_slots=False, include_paused=False ) -> Dict[str, List[UnixTimeS]]: chunk_size = 1000 pending_slots: Dict[str, List[UnixTimeS]] = {} @@ -92,6 +97,7 @@ def query_pending_payouts( addr=addr, slot_filter='status_in: ["Paying", "Canceled"]', chunk_size=chunk_size, + include_paused=include_paused, ) query2_results = [] @@ -101,6 +107,7 @@ def query_pending_payouts( addr=addr, slot_filter='status_in: ["Pending"], slot_lt: %d' % (ts_end), chunk_size=chunk_size, + include_paused=include_paused, ) merged = query1_results + query2_results From 630e1b95eda009823a8407c9dd4d6b456e9e913e Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:25:55 +0000 Subject: [PATCH 08/17] Formatting --- pdr_backend/payout/payout.py | 4 +++- pdr_backend/subgraph/subgraph_pending_payouts.py | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pdr_backend/payout/payout.py b/pdr_backend/payout/payout.py index 59caeabf0..7b3869892 100644 --- a/pdr_backend/payout/payout.py +++ b/pdr_backend/payout/payout.py @@ -92,7 +92,9 @@ def find_slots_and_payout_with_mgr( @enforce_types -def do_ocean_payout(ppss: PPSS, check_network: bool = True, include_paused: bool = False): +def do_ocean_payout( + ppss: PPSS, check_network: bool = True, include_paused: bool = False +): web3_config = ppss.web3_pp.web3_config if check_network: assert ppss.web3_pp.network == "sapphire-mainnet" diff --git a/pdr_backend/subgraph/subgraph_pending_payouts.py b/pdr_backend/subgraph/subgraph_pending_payouts.py index 0563b0c3b..cba6b9a9b 100644 --- a/pdr_backend/subgraph/subgraph_pending_payouts.py +++ b/pdr_backend/subgraph/subgraph_pending_payouts.py @@ -11,7 +11,11 @@ def _fetch_subgraph_payouts( - subgraph_url: str, addr: str, slot_filter: str, chunk_size: int, include_paused: bool = False + subgraph_url: str, + addr: str, + slot_filter: str, + chunk_size: int, + include_paused: bool = False, ) -> List[Dict[str, Any]]: """ slot_filter: string inside slot_{ ... } e.g. @@ -24,7 +28,7 @@ def _fetch_subgraph_payouts( while True: # Conditionally add the paused filter paused_filter = "" if include_paused else ", predictContract_: {paused: false}" - + query = """ { predictPredictions( From eaf34655ed18d319ebcfe4fe94366ebd9ebd2a81 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:41:26 +0000 Subject: [PATCH 09/17] feat: enhance test for paused contracts to verify pending payouts with include_paused parameter --- .../test_paused_contracts_filtered.py | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/system_tests/test_paused_contracts_filtered.py b/system_tests/test_paused_contracts_filtered.py index f2553d6f8..3a183f2e8 100644 --- a/system_tests/test_paused_contracts_filtered.py +++ b/system_tests/test_paused_contracts_filtered.py @@ -2,6 +2,7 @@ from pdr_backend.subgraph.subgraph_feed_contracts import query_feed_contracts from pdr_backend.subgraph.subgraph_pending_slots import get_pending_slots from pdr_backend.subgraph.subgraph_predictions import fetch_filtered_predictions +from pdr_backend.subgraph.subgraph_pending_payouts import query_pending_payouts from pdr_backend.util.time_types import UnixTimeS @@ -18,10 +19,12 @@ def test_paused_contracts_are_filtered_out( 3. Pause the contract 4. Wait for subgraph to sync 5. Query subgraph again - should NOT include our contract + 6. Query with include_paused=True - SHOULD include our contract """ contract_address = feed_contract1.contract_instance.address subgraph_url = web3_pp.subgraph_url + owner_address = web3_pp.web3_config.owner # Step 1: Verify contract appears in feed contracts query (not paused) feeds_before = query_feed_contracts(subgraph_url=subgraph_url) @@ -42,7 +45,15 @@ def test_paused_contracts_are_filtered_out( allowed_feeds=None, ) - # Step 3: Pause the contract + # Step 3: Query pending payouts before pausing (should work normally) + payouts_before = query_pending_payouts( + subgraph_url=subgraph_url, + addr=owner_address, + query_old_slots=False, + include_paused=False, + ) + + # Step 4: Pause the contract # Note: This requires owner permissions tx = feed_contract1.contract_instance.functions.pause().transact( web3_pp.tx_call_params() @@ -53,12 +64,12 @@ def test_paused_contracts_are_filtered_out( is_paused = feed_contract1.contract_instance.functions.paused().call() assert is_paused, "Contract should be paused" - # Step 4: Wait for subgraph to sync + # Step 5: Wait for subgraph to sync from pdr_backend.subgraph.subgraph_sync import wait_until_subgraph_syncs wait_until_subgraph_syncs(web3_pp, subgraph_url) - # Step 5: Verify contract does NOT appear in feed contracts query (paused) + # Step 6: Verify contract does NOT appear in feed contracts query (paused) feeds_after = query_feed_contracts(subgraph_url=subgraph_url) contract_found_after = contract_address.lower() in [ addr.lower() for addr in feeds_after.keys() @@ -68,7 +79,7 @@ def test_paused_contracts_are_filtered_out( not contract_found_after ), f"Paused contract {contract_address} should NOT be in feed contracts query results" - # Step 6: Verify contract does NOT appear in pending slots query (paused) + # Step 7: Verify contract does NOT appear in pending slots query (paused) slots_after = get_pending_slots( subgraph_url=subgraph_url, timestamp=current_time, @@ -85,3 +96,37 @@ def test_paused_contracts_are_filtered_out( assert ( len(slots_with_paused_contract) == 0 ), f"Paused contract {contract_address} should NOT have any slots in query results" + + # Step 8: Verify paused contract is filtered from pending payouts (include_paused=False) + payouts_without_paused = query_pending_payouts( + subgraph_url=subgraph_url, + addr=owner_address, + query_old_slots=False, + include_paused=False, + ) + + # If there were payouts for this contract before, they should be gone now + if contract_address.lower() in [addr.lower() for addr in payouts_before.keys()]: + assert contract_address.lower() not in [ + addr.lower() for addr in payouts_without_paused.keys() + ], f"Paused contract {contract_address} should NOT be in pending payouts (include_paused=False)" + + # Step 9: Verify paused contract IS included when include_paused=True + payouts_with_paused = query_pending_payouts( + subgraph_url=subgraph_url, + addr=owner_address, + query_old_slots=False, + include_paused=True, + ) + + if contract_address.lower() in [addr.lower() for addr in payouts_before.keys()]: + assert contract_address.lower() in [ + addr.lower() for addr in payouts_with_paused.keys() + ], f"Paused contract {contract_address} SHOULD be in pending payouts when include_paused=True" + + print( + f"✓ Verified paused contract {contract_address} is filtered from queries by default" + ) + print( + f"✓ Verified paused contract {contract_address} is included when include_paused=True" + ) From b34b98f82d588a8d52fedaa6bbcb0210912ff5ce Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:44:03 +0000 Subject: [PATCH 10/17] refactor: clean up test for paused contracts by improving assertions and removing redundant code --- .../test_paused_contracts_filtered.py | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/system_tests/test_paused_contracts_filtered.py b/system_tests/test_paused_contracts_filtered.py index 3a183f2e8..4367d396d 100644 --- a/system_tests/test_paused_contracts_filtered.py +++ b/system_tests/test_paused_contracts_filtered.py @@ -1,8 +1,9 @@ import pytest + from pdr_backend.subgraph.subgraph_feed_contracts import query_feed_contracts -from pdr_backend.subgraph.subgraph_pending_slots import get_pending_slots -from pdr_backend.subgraph.subgraph_predictions import fetch_filtered_predictions from pdr_backend.subgraph.subgraph_pending_payouts import query_pending_payouts +from pdr_backend.subgraph.subgraph_pending_slots import get_pending_slots +from pdr_backend.subgraph.subgraph_sync import wait_until_subgraph_syncs from pdr_backend.util.time_types import UnixTimeS @@ -29,7 +30,7 @@ def test_paused_contracts_are_filtered_out( # Step 1: Verify contract appears in feed contracts query (not paused) feeds_before = query_feed_contracts(subgraph_url=subgraph_url) contract_found_before = contract_address.lower() in [ - addr.lower() for addr in feeds_before.keys() + addr.lower() for addr in feeds_before ] # If contract is new, it should appear; if already paused, skip test @@ -38,7 +39,7 @@ def test_paused_contracts_are_filtered_out( # Step 2: Verify contract appears in pending slots query (not paused) current_time = UnixTimeS.now() - slots_before = get_pending_slots( + _ = get_pending_slots( subgraph_url=subgraph_url, timestamp=current_time, owner_addresses=None, @@ -65,21 +66,20 @@ def test_paused_contracts_are_filtered_out( assert is_paused, "Contract should be paused" # Step 5: Wait for subgraph to sync - from pdr_backend.subgraph.subgraph_sync import wait_until_subgraph_syncs - wait_until_subgraph_syncs(web3_pp, subgraph_url) - # Step 6: Verify contract does NOT appear in feed contracts query (paused) + # Step 6: Verify contract does NOT appear in feed contracts query feeds_after = query_feed_contracts(subgraph_url=subgraph_url) contract_found_after = contract_address.lower() in [ - addr.lower() for addr in feeds_after.keys() + addr.lower() for addr in feeds_after ] - assert ( - not contract_found_after - ), f"Paused contract {contract_address} should NOT be in feed contracts query results" + assert not contract_found_after, ( + f"Paused contract {contract_address} should NOT be in " + "feed contracts query results" + ) - # Step 7: Verify contract does NOT appear in pending slots query (paused) + # Step 7: Verify contract does NOT appear in pending slots query slots_after = get_pending_slots( subgraph_url=subgraph_url, timestamp=current_time, @@ -93,11 +93,13 @@ def test_paused_contracts_are_filtered_out( if slot.feed.address.lower() == contract_address.lower() ] - assert ( - len(slots_with_paused_contract) == 0 - ), f"Paused contract {contract_address} should NOT have any slots in query results" + assert len(slots_with_paused_contract) == 0, ( + f"Paused contract {contract_address} should NOT have any slots " + "in query results" + ) - # Step 8: Verify paused contract is filtered from pending payouts (include_paused=False) + # Step 8: Verify paused contract is filtered from pending payouts + # (include_paused=False) payouts_without_paused = query_pending_payouts( subgraph_url=subgraph_url, addr=owner_address, @@ -105,11 +107,14 @@ def test_paused_contracts_are_filtered_out( include_paused=False, ) - # If there were payouts for this contract before, they should be gone now - if contract_address.lower() in [addr.lower() for addr in payouts_before.keys()]: + # If there were payouts for this contract before, they should be gone + if contract_address.lower() in [addr.lower() for addr in payouts_before]: assert contract_address.lower() not in [ - addr.lower() for addr in payouts_without_paused.keys() - ], f"Paused contract {contract_address} should NOT be in pending payouts (include_paused=False)" + addr.lower() for addr in payouts_without_paused + ], ( + f"Paused contract {contract_address} should NOT be in " + "pending payouts (include_paused=False)" + ) # Step 9: Verify paused contract IS included when include_paused=True payouts_with_paused = query_pending_payouts( @@ -119,14 +124,21 @@ def test_paused_contracts_are_filtered_out( include_paused=True, ) - if contract_address.lower() in [addr.lower() for addr in payouts_before.keys()]: + if contract_address.lower() in [addr.lower() for addr in payouts_before]: assert contract_address.lower() in [ - addr.lower() for addr in payouts_with_paused.keys() - ], f"Paused contract {contract_address} SHOULD be in pending payouts when include_paused=True" + addr.lower() for addr in payouts_with_paused + ], ( + f"Paused contract {contract_address} SHOULD be in pending " + "payouts when include_paused=True" + ) print( - f"✓ Verified paused contract {contract_address} is filtered from queries by default" + f"✓ Verified paused contract {contract_address} is filtered " + "from queries by default" ) print( + f"✓ Verified paused contract {contract_address} is included " + "when include_paused=True" + ) f"✓ Verified paused contract {contract_address} is included when include_paused=True" ) From f620a024adc72ce1468b689f1b0af3920e837a2c Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:45:48 +0000 Subject: [PATCH 11/17] feat: update query_pending_payouts function docstring to clarify parameters and return type --- pdr_backend/subgraph/subgraph_pending_payouts.py | 10 ++++++++++ system_tests/test_ocean_payout.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pdr_backend/subgraph/subgraph_pending_payouts.py b/pdr_backend/subgraph/subgraph_pending_payouts.py index cba6b9a9b..0a240626d 100644 --- a/pdr_backend/subgraph/subgraph_pending_payouts.py +++ b/pdr_backend/subgraph/subgraph_pending_payouts.py @@ -80,6 +80,16 @@ def _fetch_subgraph_payouts( def query_pending_payouts( subgraph_url: str, addr: str, query_old_slots=False, include_paused=False ) -> Dict[str, List[UnixTimeS]]: + """ + Fetch pending payouts for a given address. + Parameters: + subgraph_url (str): The URL of the subgraph to query. + addr (str): The address to fetch pending payouts for. + query_old_slots (bool): Whether to query old slots (older than 3 days). + include_paused (bool): Whether to include paused contracts in the query. + Returns: + Dict[str, List[UnixTimeS]]: A dictionary mapping contract addresses to lists of pending slot timestamps. + """ chunk_size = 1000 pending_slots: Dict[str, List[UnixTimeS]] = {} addr = addr.lower() diff --git a/system_tests/test_ocean_payout.py b/system_tests/test_ocean_payout.py index e11d8fcb9..5237e47b4 100644 --- a/system_tests/test_ocean_payout.py +++ b/system_tests/test_ocean_payout.py @@ -70,7 +70,7 @@ def checksum_mock(_, y): # Additional assertions mock_query_pending_payouts.assert_called_with( - mock_web3_pp.subgraph_url, "0x1", True + mock_web3_pp.subgraph_url, "0x1", True, False ) print(mock_contract.get_payout.call_args_list) mock_contract.get_payout.assert_any_call([1, 2, 3], ["0x1"]) From 9c7d90622265205b95a4cc57def6a827422941aa Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:46:36 +0000 Subject: [PATCH 12/17] remove ai slop --- system_tests/test_paused_contracts_filtered.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/system_tests/test_paused_contracts_filtered.py b/system_tests/test_paused_contracts_filtered.py index 4367d396d..a77d7e91a 100644 --- a/system_tests/test_paused_contracts_filtered.py +++ b/system_tests/test_paused_contracts_filtered.py @@ -131,14 +131,3 @@ def test_paused_contracts_are_filtered_out( f"Paused contract {contract_address} SHOULD be in pending " "payouts when include_paused=True" ) - - print( - f"✓ Verified paused contract {contract_address} is filtered " - "from queries by default" - ) - print( - f"✓ Verified paused contract {contract_address} is included " - "when include_paused=True" - ) - f"✓ Verified paused contract {contract_address} is included when include_paused=True" - ) From 6c78a638b8e721f684ea84759883a7fd2aad457b Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 10 Nov 2025 22:33:30 +0000 Subject: [PATCH 13/17] fix linter --- pdr_backend/subgraph/subgraph_pending_payouts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdr_backend/subgraph/subgraph_pending_payouts.py b/pdr_backend/subgraph/subgraph_pending_payouts.py index 0a240626d..955f83393 100644 --- a/pdr_backend/subgraph/subgraph_pending_payouts.py +++ b/pdr_backend/subgraph/subgraph_pending_payouts.py @@ -88,7 +88,7 @@ def query_pending_payouts( query_old_slots (bool): Whether to query old slots (older than 3 days). include_paused (bool): Whether to include paused contracts in the query. Returns: - Dict[str, List[UnixTimeS]]: A dictionary mapping contract addresses to lists of pending slot timestamps. + Dict[str, List[UnixTimeS]]: contract addresses to lists of pending slot timestamps. """ chunk_size = 1000 pending_slots: Dict[str, List[UnixTimeS]] = {} From df2de89cf1da38b2b047791836ff5a9285298420 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:55:10 +0000 Subject: [PATCH 14/17] feat: add pause_predictions command and functionality to pause contract predictions --- pdr_backend/cli/cli_arguments.py | 17 +++++++++++++++++ pdr_backend/cli/cli_module.py | 9 +++++++++ pdr_backend/contract/feed_contract.py | 17 +++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/pdr_backend/cli/cli_arguments.py b/pdr_backend/cli/cli_arguments.py index 9186c6218..041b0b30a 100644 --- a/pdr_backend/cli/cli_arguments.py +++ b/pdr_backend/cli/cli_arguments.py @@ -26,6 +26,7 @@ pdr trader APPROACH PPSS_FILE NETWORK pdr claim_payouts PPSS_FILE [--include_paused] pdr claim_ROSE PPSS_FILE + pdr pause_predictions ADDRESSES PPSS_FILE NETWORK """ HELP_HELP = """ @@ -660,6 +661,19 @@ def __init__(self, description: str, command_name: str): ) +class PausePredictionsArgParser( + CustomArgParser, ACCOUNTS_Mixin, PPSS_Mixin, NETWORK_Mixin +): + # pylint: disable=unused-argument + def __init__(self, description: str, command_name: str): + super().__init__(description=description) + + self.add_arguments_bulk( + command_name, + ["ACCOUNTS", "PPSS", "NETWORK"], + ) + + # below, list each entry in defined_parsers in same order as HELP_LONG defined_parsers = { # main tools @@ -709,6 +723,9 @@ def __init__(self, description: str, command_name: str): "do_dashboard": PredictoorDashboardArgParser( "Visualize Predictoor data", "dashboard" ), + "do_pause_predictions": PausePredictionsArgParser( + "Pause predictions for multiple contracts", "pause_predictions" + ), } diff --git a/pdr_backend/cli/cli_module.py b/pdr_backend/cli/cli_module.py index da7ddccb0..58d1ba427 100644 --- a/pdr_backend/cli/cli_module.py +++ b/pdr_backend/cli/cli_module.py @@ -24,6 +24,7 @@ from pdr_backend.ppss.ppss import PPSS from pdr_backend.pred_submitter.deploy import deploy_pred_submitter_mgr_contract from pdr_backend.predictoor.predictoor_agent import PredictoorAgent +from pdr_backend.publisher.pause_predictions import pause_predictions from pdr_backend.publisher.publish_assets import publish_assets from pdr_backend.sim.multisim_engine import MultisimEngine from pdr_backend.sim.sim_dash import sim_dash @@ -309,3 +310,11 @@ def do_arima_plots(args, nested_args=None): # pylint: disable=unused-argument def do_dashboard(args, nested_args=None): predictoor_dash(args.PPSS, args.debug_mode) + + +@enforce_types +# pylint: disable=unused-argument +def do_pause_predictions(args, nested_args=None): + ppss = args.PPSS + addresses = args.ACCOUNTS + pause_predictions(ppss.web3_pp, addresses) diff --git a/pdr_backend/contract/feed_contract.py b/pdr_backend/contract/feed_contract.py index 50a8dacac..6e6a78120 100644 --- a/pdr_backend/contract/feed_contract.py +++ b/pdr_backend/contract/feed_contract.py @@ -386,6 +386,23 @@ def erc721_addr(self) -> str: """What's the ERC721 address from which this ERC20 feed was created?""" return self.contract_instance.functions.getERC721Address().call() + def pause_predictions(self, wait_for_receipt=True): + """Pause predictions for this feed. Can only be called by the owner.""" + call_params = self.web3_pp.tx_call_params() + try: + tx = self.contract_instance.functions.pausePredictions().transact( + call_params + ) + logger.info("Pause predictions: txhash=%s", tx.hex()) + + if not wait_for_receipt: + return tx + + return self.config.w3.eth.wait_for_transaction_receipt(tx) + except Exception as e: + logger.error(e) + return None + # ========================================================================= # utilities for testing From fe9ea339cd6551bb7f2b4958692c0544251ba336 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Sun, 7 Dec 2025 23:22:43 +0000 Subject: [PATCH 15/17] fix: correct token price calculation in publish_assets function --- pdr_backend/publisher/publish_assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdr_backend/publisher/publish_assets.py b/pdr_backend/publisher/publish_assets.py index 7a076085a..58c8ea4b3 100644 --- a/pdr_backend/publisher/publish_assets.py +++ b/pdr_backend/publisher/publish_assets.py @@ -9,7 +9,7 @@ logger = logging.getLogger("publisher") _CUT = Eth(0.2) -_RATE = Eth(3 / (1 + float(_CUT) + 0.001)) # token price +_RATE = Eth(1 / (1 + float(_CUT) + 0.001)) # token price _S_PER_SUBSCRIPTION = 60 * 60 * 24 From 6a3513f05e1db705ed7b1d672501e685f58a7f9a Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Sun, 7 Dec 2025 23:29:31 +0000 Subject: [PATCH 16/17] updated rate --- pdr_backend/publisher/test/test_publish_assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdr_backend/publisher/test/test_publish_assets.py b/pdr_backend/publisher/test/test_publish_assets.py index 5c4ea25fd..e79fde30a 100644 --- a/pdr_backend/publisher/test/test_publish_assets.py +++ b/pdr_backend/publisher/test/test_publish_assets.py @@ -35,7 +35,7 @@ def _test_barge(network, monkeypatch): feed=ArgFeed("binance", "close", "ETH/USDT", "5m"), trueval_submitter_addr="0xe2DD09d719Da89e5a3D0F2549c7E24566e947260", feeCollector_addr="0xe2DD09d719Da89e5a3D0F2549c7E24566e947260", - rate=Eth(3 / (1 + 0.2 + 0.001)), + rate=Eth(1 / (1 + 0.2 + 0.001)), cut=Eth(0.2), web3_pp=web3_pp, ) From f1226c61ad58472f3e8a0ff6c35d8f157491c1a8 Mon Sep 17 00:00:00 2001 From: trizin <25263018+trizin@users.noreply.github.com> Date: Sun, 7 Dec 2025 23:32:12 +0000 Subject: [PATCH 17/17] feat: implement pause_predictions function to manage contract predictions --- pdr_backend/publisher/pause_predictions.py | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 pdr_backend/publisher/pause_predictions.py diff --git a/pdr_backend/publisher/pause_predictions.py b/pdr_backend/publisher/pause_predictions.py new file mode 100644 index 000000000..4139e3b8d --- /dev/null +++ b/pdr_backend/publisher/pause_predictions.py @@ -0,0 +1,48 @@ +import logging +from typing import List + +from enforce_typing import enforce_types + +from pdr_backend.contract.feed_contract import FeedContract +from pdr_backend.ppss.web3_pp import Web3PP + +logger = logging.getLogger("pause_predictions") + + +@enforce_types +def pause_predictions(web3_pp: Web3PP, contract_addresses: List[str]): + """ + Pause predictions for a list of feed contracts. + + @arguments + web3_pp: Web3PP instance with network configuration + contract_addresses: List of contract addresses to pause + """ + logger.info("Pausing predictions on network = %s", web3_pp.network) + logger.info("Number of contracts to pause: %d", len(contract_addresses)) + + successful = [] + failed = [] + + for address in contract_addresses: + logger.info("Pausing predictions for contract: %s", address) + try: + feed_contract = FeedContract(web3_pp, address) + tx = feed_contract.pause_predictions(wait_for_receipt=True) + + if tx is not None: + logger.info("Successfully paused predictions for %s", address) + successful.append(address) + else: + logger.error("Failed to pause predictions for %s", address) + failed.append(address) + except Exception as e: + logger.error("Error pausing predictions for %s: %s", address, e) + failed.append(address) + + logger.info("Done pausing predictions.") + logger.info("Successfully paused: %d", len(successful)) + logger.info("Failed to pause: %d", len(failed)) + + if failed: + logger.warning("Failed contracts: %s", failed)