fix(indexer): factory pair provenance on discover_new_pair (#311) #790

Merged
PlasticDigits merged 1 commit from cursor/gitlab-issue-workflow-482b into main 2026-06-05 10:37:45 +00:00
PlasticDigits commented 2026-06-05 10:08:08 +00:00 (Migrated from gitlab.com)

Summary

Closes follow-up to GitLab #285 (wasm emitter scoping). Opportunistic discover_new_pair now verifies each candidate pair is listed in the configured factory via Pair { asset_infos } reverse lookup before upserting to Postgres. sync_all_pairs is unchanged.

Issue: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/311

Acceptance checklist

Criterion Verification Result
discover_new_pair rejects pairs not in configured factory cargo test --lib pair_discovery::tests PASS
sync_all_pairs path unchanged for legitimate pairs Code review: no changes to sync_all_pairs PASS
Forged pair events do not create indexed pair (with #285 scoping) Parser forged-emitter tests + provenance gate PASS
API/docs state provenance requirement docs/indexer-invariants.md (P1), docs/integrators.md, indexer/.env.example PASS
Attacker pair query but not in factory → not indexed Wiremock unit tests in pair_discovery.rs PASS
Empty FACTORY_ADDRESS dev: warn + skip provenance verify_factory_provenance_skipped_when_factory_empty PASS
Prod empty factory config error Existing config.rs tests (unchanged) PASS

Verification for third parties

cd indexer && cargo test --lib pair_discovery::tests
cd indexer && cargo test --lib

Issue left open until MR merge.


Note

Medium Risk
Changes which contracts get indexed on the opportunistic discovery path (security-sensitive) but failures are non-blocking per event and prod already requires a configured factory.

Overview
Opportunistic discover_new_pair (used when the parser sees events for an unknown pair) now requires factory listing before upserting: it reads the pair’s Pair query, checks contract_addr matches the queried address, then calls the configured factory’s Pair { asset_infos } reverse lookup and insists the returned address equals the candidate. sync_all_pairs is unchanged.

The block parser passes config.factory_address into every code path that may auto-discover (swaps, liquidity, limit fills/placements/cancellations/parked/claims). Failed provenance still warns and skips the event without failing the block. Empty FACTORY_ADDRESS logs a warning and skips the check (dev only); prod already rejects empty factory via config.rs.

Adds FactoryPairResponse, wiremock unit tests for verify_factory_provenance, and operator/integrator docs plus .env.example notes for FACTORY_ADDRESS (GitLab #311, follow-up to #285).

Reviewed by Cursor Bugbot for commit ce9040cb93. Bugbot is set up for automated code reviews on this repo. Configure here.

## Summary Closes follow-up to GitLab #285 (wasm emitter scoping). Opportunistic `discover_new_pair` now verifies each candidate pair is listed in the configured factory via `Pair { asset_infos }` reverse lookup before upserting to Postgres. `sync_all_pairs` is unchanged. Issue: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/311 ## Acceptance checklist | Criterion | Verification | Result | |-----------|--------------|--------| | `discover_new_pair` rejects pairs not in configured factory | `cargo test --lib pair_discovery::tests` | PASS | | `sync_all_pairs` path unchanged for legitimate pairs | Code review: no changes to `sync_all_pairs` | PASS | | Forged pair events do not create indexed pair (with #285 scoping) | Parser forged-emitter tests + provenance gate | PASS | | API/docs state provenance requirement | `docs/indexer-invariants.md` (P1), `docs/integrators.md`, `indexer/.env.example` | PASS | | Attacker pair query but not in factory → not indexed | Wiremock unit tests in `pair_discovery.rs` | PASS | | Empty `FACTORY_ADDRESS` dev: warn + skip provenance | `verify_factory_provenance_skipped_when_factory_empty` | PASS | | Prod empty factory config error | Existing `config.rs` tests (unchanged) | PASS | ## Verification for third parties ```bash cd indexer && cargo test --lib pair_discovery::tests cd indexer && cargo test --lib ``` Issue left **open** until MR merge. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes which contracts get indexed on the opportunistic discovery path (security-sensitive) but failures are non-blocking per event and prod already requires a configured factory. > > **Overview** > Opportunistic **`discover_new_pair`** (used when the parser sees events for an unknown pair) now **requires factory listing** before upserting: it reads the pair’s `Pair` query, checks **`contract_addr` matches the queried address**, then calls the configured factory’s **`Pair { asset_infos }`** reverse lookup and insists the returned address equals the candidate. **`sync_all_pairs`** is unchanged. > > The block parser passes **`config.factory_address`** into every code path that may auto-discover (swaps, liquidity, limit fills/placements/cancellations/parked/claims). Failed provenance still **warns and skips the event** without failing the block. **Empty `FACTORY_ADDRESS`** logs a warning and skips the check (dev only); prod already rejects empty factory via **`config.rs`**. > > Adds **`FactoryPairResponse`**, wiremock unit tests for **`verify_factory_provenance`**, and operator/integrator docs plus **`.env.example`** notes for **`FACTORY_ADDRESS`** (GitLab **#311**, follow-up to **#285**). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ce9040cb936d4c4be9080a540e0bada13b3709eb. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
PlasticDigits commented 2026-06-05 10:08:13 +00:00 (Migrated from gitlab.com)

mentioned in issue #311

mentioned in issue #311
ghost1 commented 2026-06-05 10:08:16 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
ghost1 commented 2026-06-05 10:08:22 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-05 10:10:51 +00:00 (Migrated from gitlab.com)

Security review

Commit reviewed: ce9040cb936d4c4be9080a540e0bada13b3709eb

Scope: discover_new_pair factory provenance gate (verify_factory_provenance), parser wiring (factory_addr plumbed through all discovery call sites), FactoryPairResponse LCD type, docs/env notes (P1 invariant).

Method: Traced attacker-controlled inputs (wasm _contract_address from chain events → pair LCD query → factory Pair { asset_infos } reverse lookup) to the indexing sink (sync_single_pair / upsert_pair). Cross-checked against #285 emitter scoping, factory pair_key canonicalization (sorted asset keys), and RUN_MODE=prod empty-FACTORY_ADDRESS rejection in config.rs. Ran cargo test --lib pair_discovery::tests (4/4 pass).

Outcome: FINDINGS: 0 medium+

This MR closes the opportunistic-discovery gap from #285: unlisted / clone pair contracts that mimic the pair query API are rejected unless the configured factory maps the same asset_infos to the emitter address. The pair_info.contract_addr == pair_contract_addr guard blocks query responses that claim a different on-chain identity. Dev-only empty FACTORY_ADDRESS skips provenance (documented; prod startup fails closed).

Inline threads: none (no medium+ findings).

## Security review **Commit reviewed:** `ce9040cb936d4c4be9080a540e0bada13b3709eb` **Scope:** `discover_new_pair` factory provenance gate (`verify_factory_provenance`), parser wiring (`factory_addr` plumbed through all discovery call sites), `FactoryPairResponse` LCD type, docs/env notes (P1 invariant). **Method:** Traced attacker-controlled inputs (wasm `_contract_address` from chain events → pair LCD query → factory `Pair { asset_infos }` reverse lookup) to the indexing sink (`sync_single_pair` / `upsert_pair`). Cross-checked against #285 emitter scoping, factory `pair_key` canonicalization (sorted asset keys), and `RUN_MODE=prod` empty-`FACTORY_ADDRESS` rejection in `config.rs`. Ran `cargo test --lib pair_discovery::tests` (4/4 pass). **Outcome:** `FINDINGS: 0` medium+ This MR closes the opportunistic-discovery gap from #285: unlisted / clone pair contracts that mimic the pair query API are rejected unless the configured factory maps the same `asset_infos` to the emitter address. The `pair_info.contract_addr == pair_contract_addr` guard blocks query responses that claim a different on-chain identity. Dev-only empty `FACTORY_ADDRESS` skips provenance (documented; prod startup fails closed). **Inline threads:** none (no medium+ findings).
PlasticDigits commented 2026-06-05 10:37:45 +00:00 (Migrated from gitlab.com)

mentioned in commit 104e28d603

mentioned in commit 104e28d6035c85c46d3b9fdf545fee7dd3c11c10
PlasticDigits (Migrated from gitlab.com) merged commit 104e28d603 into main 2026-06-05 10:37:45 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic!790
No description provided.