Fix swap price orientation for CG/CMC OHLC (#466) #1008

Merged
PlasticDigits merged 1 commit from fix/466-swap-price-orientation into main 2026-07-07 02:34:59 +00:00
PlasticDigits commented 2026-07-07 02:18:17 +00:00 (Migrated from gitlab.com)

Summary

Fixes GitLab #466: the indexer stored swap_events.price as raw return_amount / offer_amount with no base/quote orientation. Two-sided trading mixed P and 1/P in the same column, corrupting 24h high/low, last_price, bid/ask, candles, and CG/CMC feeds.

Changes:

  • New swap_orientation::orient_swap_leg — canonical quote per base (asset_1 / asset_0) at index time in process_swap and candle builder volume buckets.
  • 24h stats SQL (get_24h_stats_for_pair, get_24h_stats_all_pairs), pair_volume_24h refresh, and rebuild_candles_from_swaps bucket volumes by asset id, not raw offer/return.
  • Migration 20260707000000_* backfills historical swap_events.price and truncates candles for rebuild.
  • Docs: docs/indexer-invariants.md, docs/CG_CMC_COMPLIANCE.md.

Acceptance checklist

Criterion Verification Result
Price normalized to quote-per-base at write time orient_swap_leg unit tests + parser uses module PASS
Reverse-direction trade stores same price as forward oriented_two_sided_swaps_high_low_are_not_reciprocals PASS
24h high/low not reciprocals across two-sided window same integration test (MAX/MIN on oriented prices) PASS
Volume aggregation buckets base/quote by asset id oriented_volume_aggregation_sums_base_and_quote_legs PASS
Batch CG/CMC stats unchanged contract cargo test --test api_aggregator_batch PASS
Pair volume rollup parity cargo test --test indexer_pair_volume_pagination PASS
Historical price backfill migration 20260707000000_normalize_swap_price_orientation.sql PASS (SQL review)
Candle rebuild after migration TRUNCATE candles in migration; rebuild via seed-qa --clean or rebuild_candles_from_swaps SKIP (ops step post-deploy)
Live QA /cg/tickers low ≠ 1/high Requires QA indexer redeploy SKIP (needs deploy)

Verification for third parties

make setup-indexer-postgres
cd indexer && cargo test --lib swap_orientation
cd indexer && cargo test --test swap_price_orientation -- --test-threads=1
cd indexer && cargo test --test api_aggregator_batch -- --test-threads=1
cd indexer && cargo test --test indexer_pair_volume_pagination -- --test-threads=1

After deploy + migration on an indexer with bidirectional history:

  1. Run migration (automatic on indexer start).
  2. Rebuild candles: cargo run -- seed-qa --clean on QA, or per-pair rebuild_candles_from_swaps.
  3. curl -s $INDEXER/cg/tickers | jq '.[] | select(.low != null) | {id: .ticker_id, last: .last_price, high: .high, low: .low}' — confirm low is not ≈ 1/high.

Follow-ups

  • Post-deploy ops runbook entry for production candle rebuild after migration (if not covered by existing seed-qa docs).
## Summary Fixes GitLab #466: the indexer stored `swap_events.price` as raw `return_amount / offer_amount` with no base/quote orientation. Two-sided trading mixed **P** and **1/P** in the same column, corrupting 24h high/low, `last_price`, bid/ask, candles, and CG/CMC feeds. **Changes:** - New `swap_orientation::orient_swap_leg` — canonical **quote per base** (`asset_1` / `asset_0`) at index time in `process_swap` and candle builder volume buckets. - 24h stats SQL (`get_24h_stats_for_pair`, `get_24h_stats_all_pairs`), `pair_volume_24h` refresh, and `rebuild_candles_from_swaps` bucket volumes by asset id, not raw offer/return. - Migration `20260707000000_*` backfills historical `swap_events.price` and truncates `candles` for rebuild. - Docs: `docs/indexer-invariants.md`, `docs/CG_CMC_COMPLIANCE.md`. ## Acceptance checklist | Criterion | Verification | Result | |-----------|--------------|--------| | Price normalized to quote-per-base at write time | `orient_swap_leg` unit tests + parser uses module | PASS | | Reverse-direction trade stores same price as forward | `oriented_two_sided_swaps_high_low_are_not_reciprocals` | PASS | | 24h high/low not reciprocals across two-sided window | same integration test (`MAX`/`MIN` on oriented prices) | PASS | | Volume aggregation buckets base/quote by asset id | `oriented_volume_aggregation_sums_base_and_quote_legs` | PASS | | Batch CG/CMC stats unchanged contract | `cargo test --test api_aggregator_batch` | PASS | | Pair volume rollup parity | `cargo test --test indexer_pair_volume_pagination` | PASS | | Historical price backfill migration | `20260707000000_normalize_swap_price_orientation.sql` | PASS (SQL review) | | Candle rebuild after migration | `TRUNCATE candles` in migration; rebuild via `seed-qa --clean` or `rebuild_candles_from_swaps` | SKIP (ops step post-deploy) | | Live QA `/cg/tickers` low ≠ 1/high | Requires QA indexer redeploy | SKIP (needs deploy) | ## Verification for third parties ```bash make setup-indexer-postgres cd indexer && cargo test --lib swap_orientation cd indexer && cargo test --test swap_price_orientation -- --test-threads=1 cd indexer && cargo test --test api_aggregator_batch -- --test-threads=1 cd indexer && cargo test --test indexer_pair_volume_pagination -- --test-threads=1 ``` After deploy + migration on an indexer with bidirectional history: 1. Run migration (automatic on indexer start). 2. Rebuild candles: `cargo run -- seed-qa --clean` on QA, or per-pair `rebuild_candles_from_swaps`. 3. `curl -s $INDEXER/cg/tickers | jq '.[] | select(.low != null) | {id: .ticker_id, last: .last_price, high: .high, low: .low}'` — confirm `low` is not `≈ 1/high`. ## Follow-ups - Post-deploy ops runbook entry for production candle rebuild after migration (if not covered by existing `seed-qa` docs).
ghost1 commented 2026-07-07 02:18:20 +00:00 (Migrated from gitlab.com)

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the [Cursor dashboard](https://www.cursor.com/dashboard/bugbot) to get automatic reviews on future PRs.
PlasticDigits commented 2026-07-07 02:20:10 +00:00 (Migrated from gitlab.com)

Security review — MR !1008

Commit reviewed: ff28fbba19d3fdf38b8d3300331f6589c2d2cb98
Scope: Swap price/volume orientation fix for CG/CMC OHLC (#466) — swap_orientation::orient_swap_leg, indexer parser/candle path, 24h stats & volume rollup SQL, migration 20260707000000_normalize_swap_price_orientation.sql, docs/tests.

Method

  • Full MR diff inspected; attacker-controlled input traced to sinks (on-chain swap attrs → parser → DB aggregates → read-only CG/CMC API).
  • Prior MR discussion threads checked (none prior security-review findings).
  • Candidate areas: SQL injection, authz bypass, unsafe migration side effects, division-by-zero/DoS, market-data manipulation via new code paths.

Outcome: FINDINGS: 0 (no medium+)

No inline review threads — nothing met the medium+ bar with a plausible exploit path on this diff.

Notes (informational, below reporting threshold)

  • SQL: New/changed queries use parameterized binds ($1…); candle interval string is allowlisted before format! — no injection surface added.
  • Input trust model: Swap fields remain chain-sourced wasm attrs resolved via existing asset_resolver; orientation is deterministic math on amounts already indexed — no new unauthenticated write API.
  • Migration TRUNCATE candles: One-time deploy availability concern (empty candles until rebuild); not attacker-triggerable without DB/migration control.
  • Market-data integrity: This change corrects reciprocal-price corruption in OHLC/volume feeds; wash-trade volume inflation is inherent to public DEX indexing and not introduced here.

Automated security review (Cursor Cloud Agent). Label block:security not applied.

## Security review — MR !1008 **Commit reviewed:** `ff28fbba19d3fdf38b8d3300331f6589c2d2cb98` **Scope:** Swap price/volume orientation fix for CG/CMC OHLC (#466) — `swap_orientation::orient_swap_leg`, indexer parser/candle path, 24h stats & volume rollup SQL, migration `20260707000000_normalize_swap_price_orientation.sql`, docs/tests. ### Method - Full MR diff inspected; attacker-controlled input traced to sinks (on-chain swap attrs → parser → DB aggregates → read-only CG/CMC API). - Prior MR discussion threads checked (none prior security-review findings). - Candidate areas: SQL injection, authz bypass, unsafe migration side effects, division-by-zero/DoS, market-data manipulation via new code paths. ### Outcome: **FINDINGS: 0** (no medium+) No inline review threads — nothing met the medium+ bar with a plausible exploit path on this diff. ### Notes (informational, below reporting threshold) - **SQL:** New/changed queries use parameterized binds (`$1`…); candle interval string is allowlisted before `format!` — no injection surface added. - **Input trust model:** Swap fields remain chain-sourced wasm attrs resolved via existing `asset_resolver`; orientation is deterministic math on amounts already indexed — no new unauthenticated write API. - **Migration `TRUNCATE candles`:** One-time deploy availability concern (empty candles until rebuild); not attacker-triggerable without DB/migration control. - **Market-data integrity:** This change *corrects* reciprocal-price corruption in OHLC/volume feeds; wash-trade volume inflation is inherent to public DEX indexing and not introduced here. --- Automated security review (Cursor Cloud Agent). Label `block:security` not applied.
PlasticDigits commented 2026-07-07 02:34:59 +00:00 (Migrated from gitlab.com)

mentioned in commit a5bbd55e16

mentioned in commit a5bbd55e16b3c5b753a1d8793aeb4ba410c59437
PlasticDigits (Migrated from gitlab.com) merged commit a5bbd55e16 into main 2026-07-07 02:34:59 +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!1008
No description provided.