diff --git a/pdr_backend/analytics/check_network.py b/pdr_backend/analytics/check_network.py index 2f0acf3fb..6784b1dae 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/cli/cli_arguments.py b/pdr_backend/cli/cli_arguments.py index 44f82f4c2..041b0b30a 100644 --- a/pdr_backend/cli/cli_arguments.py +++ b/pdr_backend/cli/cli_arguments.py @@ -24,8 +24,9 @@ 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 + pdr pause_predictions ADDRESSES PPSS_FILE NETWORK """ HELP_HELP = """ @@ -276,6 +277,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 +566,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 @@ -641,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 @@ -690,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 e8202b705..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 @@ -129,7 +130,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 @@ -308,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/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/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 diff --git a/pdr_backend/payout/payout.py b/pdr_backend/payout/payout.py index dad639ae5..7b3869892 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,9 @@ 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 +103,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/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) 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 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, ) 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..955f83393 100644 --- a/pdr_backend/subgraph/subgraph_pending_payouts.py +++ b/pdr_backend/subgraph/subgraph_pending_payouts.py @@ -11,20 +11,28 @@ 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 } }, + where: { user: "%s", payout: null, slot_: { %s%s } }, first: %d, skip: %d ) { @@ -40,6 +48,7 @@ def _fetch_subgraph_payouts( """ % ( addr, slot_filter, + paused_filter, chunk_size, offset, ) @@ -69,8 +78,18 @@ 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]]: + """ + 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]]: contract addresses to lists of pending slot timestamps. + """ chunk_size = 1000 pending_slots: Dict[str, List[UnixTimeS]] = {} addr = addr.lower() @@ -92,6 +111,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 +121,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 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 ) { 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"]) diff --git a/system_tests/test_paused_contracts_filtered.py b/system_tests/test_paused_contracts_filtered.py new file mode 100644 index 000000000..a77d7e91a --- /dev/null +++ b/system_tests/test_paused_contracts_filtered.py @@ -0,0 +1,133 @@ +import pytest + +from pdr_backend.subgraph.subgraph_feed_contracts import query_feed_contracts +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 + + +def test_paused_contracts_are_filtered_out( + web3_pp, + feed_contract1, +): + """ + 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 + 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) + contract_found_before = contract_address.lower() in [ + addr.lower() for addr in feeds_before + ] + + # 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() + _ = get_pending_slots( + subgraph_url=subgraph_url, + timestamp=current_time, + owner_addresses=None, + allowed_feeds=None, + ) + + # 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() + ) + web3_pp.w3.eth.wait_for_transaction_receipt(tx) + + # Verify contract is paused + is_paused = feed_contract1.contract_instance.functions.paused().call() + assert is_paused, "Contract should be paused" + + # Step 5: Wait for subgraph to sync + wait_until_subgraph_syncs(web3_pp, subgraph_url) + + # 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 + ] + + 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 + 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" + ) + + # 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 + 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 + ], ( + 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]: + assert contract_address.lower() in [ + addr.lower() for addr in payouts_with_paused + ], ( + f"Paused contract {contract_address} SHOULD be in pending " + "payouts when include_paused=True" + )