fix(frontend): symmetric BID ladder crossing guard when best ask empty (#385) #914

Merged
PlasticDigits merged 1 commit from fix/issue-385-ladder-bid-crossing-guard into main 2026-06-16 15:36:50 +00:00
PlasticDigits commented 2026-06-16 15:34:14 +00:00 (Migrated from gitlab.com)

Summary

Fixes #385 — follow-up to #297 / MR !757. The limit ladder crossing guard blocked ASK rungs below best_bid but silently allowed BID rungs above market when best_ask was empty or not yet resolved.

Root cause: limitBidCrossesBestAsk returns false when bestAsk is missing, so the ladder guard was skipped while ASK still blocked against a populated bid book. The retail TradeOrderTicket also has a tape/pool reference gate; the ladder panel only had book-head checks.

Changes:

  • Add describeLimitCrossingBlockerWithRef — book head first, then tape/AMM refToken1PerToken0 fallback when the opposite book side is empty
  • Wire reference props from /limits (useLimitOrderPriceRefBundle) into LimitOrderLadderPanel
  • Seed useTradeBestBookPrices from cached limitBookPage infinite-query data when dedicated head fetches are still empty
  • Block submit while book/reference resolution is in flight for the needed opposite side
  • Regression tests + docs/skills cross-links

Acceptance checklist

Criterion Verification Result
BID ladder with rungs above market shows N of M rungs will cross the market… cd frontend-dapp && npm test -- LimitOrderLadderPanel.crossing (case: null bestAsk + ref 1.1, prices 15–18) PASS
BID ladder submit disabled when crossing Same Vitest: ladder-place-submit disabled PASS
ASK ladder below best_bid still blocked Vitest existing case (prices 0.1–0.5 vs bid 1) PASS
Non-crossing bid ladder within spread allowed Vitest existing case (default 0.95–1.05 vs ask 1.5) PASS
describeLimitCrossingBlockerWithRef unit coverage cd frontend-dapp && npm test -- limitOrderNonCrossing PASS
Mutation re-check before broadcast Code: mutationFn loops rungs with describeLimitCrossingBlockerWithRef PASS
Frontend lint make lint-frontend PASS
Manual browser: CORAL/EMBER BID ladder 15–18 blocked on LocalTerra make setup-cloud-localterra && make dev → /limits Ladder tab SKIP (LocalTerra not provisioned in agent VM this session; covered by regression test reproducing empty bestAsk)

Verification for third parties

# Unit / component regression
cd frontend-dapp && npm test -- LimitOrderLadderPanel.crossing limitOrderNonCrossing

# Lint
make lint-frontend

Manual (LocalTerra + Keplr):

  1. make setup-cloud-localterra && make dev
  2. Open http://127.0.0.1:5173/limits, connect wallet, select CORAL/EMBER
  3. Ladder tab → BID → start 15, end 18 → expect crossing warning + disabled submit
  4. ASK → start 0.1, end 0.5 → expect same guard (regression)

Blockers

None for merge from automated checks. Manual LocalTerra walkthrough recommended before release QA.

## Summary Fixes **#385** — follow-up to **#297** / MR !757. The limit ladder crossing guard blocked ASK rungs below `best_bid` but silently allowed BID rungs above market when `best_ask` was empty or not yet resolved. **Root cause:** `limitBidCrossesBestAsk` returns `false` when `bestAsk` is missing, so the ladder guard was skipped while ASK still blocked against a populated bid book. The retail `TradeOrderTicket` also has a tape/pool reference gate; the ladder panel only had book-head checks. **Changes:** - Add `describeLimitCrossingBlockerWithRef` — book head first, then tape/AMM `refToken1PerToken0` fallback when the opposite book side is empty - Wire reference props from `/limits` (`useLimitOrderPriceRefBundle`) into `LimitOrderLadderPanel` - Seed `useTradeBestBookPrices` from cached `limitBookPage` infinite-query data when dedicated head fetches are still empty - Block submit while book/reference resolution is in flight for the needed opposite side - Regression tests + docs/skills cross-links ## Acceptance checklist | Criterion | Verification | Result | |-----------|--------------|--------| | BID ladder with rungs above market shows `N of M rungs will cross the market…` | `cd frontend-dapp && npm test -- LimitOrderLadderPanel.crossing` (case: null `bestAsk` + ref 1.1, prices 15–18) | **PASS** | | BID ladder submit disabled when crossing | Same Vitest: `ladder-place-submit` disabled | **PASS** | | ASK ladder below `best_bid` still blocked | Vitest existing case (prices 0.1–0.5 vs bid 1) | **PASS** | | Non-crossing bid ladder within spread allowed | Vitest existing case (default 0.95–1.05 vs ask 1.5) | **PASS** | | `describeLimitCrossingBlockerWithRef` unit coverage | `cd frontend-dapp && npm test -- limitOrderNonCrossing` | **PASS** | | Mutation re-check before broadcast | Code: `mutationFn` loops rungs with `describeLimitCrossingBlockerWithRef` | **PASS** | | Frontend lint | `make lint-frontend` | **PASS** | | Manual browser: CORAL/EMBER BID ladder 15–18 blocked on LocalTerra | `make setup-cloud-localterra && make dev` → `/limits` Ladder tab | **SKIP** (LocalTerra not provisioned in agent VM this session; covered by regression test reproducing empty `bestAsk`) | ## Verification for third parties ```bash # Unit / component regression cd frontend-dapp && npm test -- LimitOrderLadderPanel.crossing limitOrderNonCrossing # Lint make lint-frontend ``` **Manual (LocalTerra + Keplr):** 1. `make setup-cloud-localterra && make dev` 2. Open `http://127.0.0.1:5173/limits`, connect wallet, select CORAL/EMBER 3. Ladder tab → BID → start `15`, end `18` → expect crossing warning + disabled submit 4. ASK → start `0.1`, end `0.5` → expect same guard (regression) ## Blockers None for merge from automated checks. Manual LocalTerra walkthrough recommended before release QA.
PlasticDigits commented 2026-06-16 15:36:51 +00:00 (Migrated from gitlab.com)

mentioned in commit 8a5c9e85a2

mentioned in commit 8a5c9e85a2a2ce6c6e500a1e0ac4cb1faf79af90
PlasticDigits (Migrated from gitlab.com) merged commit 8a5c9e85a2 into main 2026-06-16 15:36:51 +00:00
PlasticDigits commented 2026-06-16 15:37:50 +00:00 (Migrated from gitlab.com)

Security review — MR !914

Commit reviewed: 5850aa28ba51d5ab36fdeaa7230c448cba24e8c1

Scope: Symmetric BID ladder crossing guard when best_ask is empty — describeLimitCrossingBlockerWithRef, useTradeBestBookPrices cache seeding, ladder panel wiring from useLimitOrderPriceRefBundle, tests/docs.

Outcome: FINDINGS: 0 medium+

Prior threads: No prior security-review notes on this MR; nothing to re-report.

Analysis summary

This change extends an existing client-only post-only UX guard (documented as non-authoritative; the pair contract still accepts crossing limits on-chain). Review traced attacker-controlled inputs (ladder prices, pair selection) to sinks (inline guard messages, mutationFn re-check before broadcast).

Area Result
Injection / XSS Guard copy is rendered as React text nodes; ref is a finite number; book prices use existing decimal-string helpers. No new HTML/dangerouslySetInnerHTML paths.
Authn / authz N/A — local wallet-signed txs only; no server endpoints added.
Indexer / LCD data trust refToken1PerToken0 and book heads come from the same indexer/LCD sources already used by TradeOrderTicket / evaluateLimitOrderPricePlaceGate. Misreported market data could mislead the placing user about crossing risk; it does not grant access to third-party funds or bypass on-chain checks.
Cache seeding (limitBookPage) Query keys are scoped by pairAddress + side; dedicated head fetch takes precedence when non-null. Stale cache may affect submit UX for the connected user only.
Ref-resolution gap When opposite book is empty and reference resolution fails/returns null, the ladder may still allow placement — same class of self-directed trading risk as pre-MR behavior with empty best_ask, and stricter than before when a reference is available. Not an exploitable cross-user boundary.
Supply chain No dependency changes.

Inline threads: None (no medium+ findings).

Security review: no medium+ findings on this diff.

## Security review — MR !914 **Commit reviewed:** `5850aa28ba51d5ab36fdeaa7230c448cba24e8c1` **Scope:** Symmetric BID ladder crossing guard when `best_ask` is empty — `describeLimitCrossingBlockerWithRef`, `useTradeBestBookPrices` cache seeding, ladder panel wiring from `useLimitOrderPriceRefBundle`, tests/docs. **Outcome:** `FINDINGS: 0` medium+ **Prior threads:** No prior security-review notes on this MR; nothing to re-report. ### Analysis summary This change extends an existing **client-only post-only UX guard** (documented as non-authoritative; the pair contract still accepts crossing limits on-chain). Review traced attacker-controlled inputs (ladder prices, pair selection) to sinks (inline guard messages, `mutationFn` re-check before broadcast). | Area | Result | |------|--------| | Injection / XSS | Guard copy is rendered as React text nodes; `ref` is a finite `number`; book prices use existing decimal-string helpers. No new HTML/`dangerouslySetInnerHTML` paths. | | Authn / authz | N/A — local wallet-signed txs only; no server endpoints added. | | Indexer / LCD data trust | `refToken1PerToken0` and book heads come from the same indexer/LCD sources already used by `TradeOrderTicket` / `evaluateLimitOrderPricePlaceGate`. Misreported market data could mislead the placing user about crossing risk; it does not grant access to third-party funds or bypass on-chain checks. | | Cache seeding (`limitBookPage`) | Query keys are scoped by `pairAddress` + side; dedicated head fetch takes precedence when non-null. Stale cache may affect submit UX for the connected user only. | | Ref-resolution gap | When opposite book is empty and reference resolution fails/returns null, the ladder may still allow placement — same class of self-directed trading risk as pre-MR behavior with empty `best_ask`, and stricter than before when a reference is available. Not an exploitable cross-user boundary. | | Supply chain | No dependency changes. | **Inline threads:** None (no medium+ findings). Security review: **no medium+ findings** on this diff.
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!914
No description provided.