SEC-B05: disable swap and LP CTAs when pair is paused (#395) #925

Merged
PlasticDigits merged 1 commit from issue-395-pair-pause-swap-pool-cta into main 2026-06-19 12:45:50 +00:00
PlasticDigits commented 2026-06-19 10:59:29 +00:00 (Migrated from gitlab.com)

Summary

Addresses GitLab #395 (SEC-B05): SwapPage and PoolPage now query LCD is_paused for the active route pair(s) via shared hook usePairPaused, show pause banners, and disable swap / provide / withdraw submit CTAs before users broadcast txs that would revert on-chain under invariant L6.

  • Swap (/): checks all pair contract addresses on the resolved swap route; CTA label Pair is paused (after wrap-mapper pause, before blacklist).
  • Pool (/pool): checks the selected pool card pair; provide and withdraw CTAs disabled with matching banner.
  • Docs cross-linked: frontend.md, testing.md, user-incident-faq.md, contracts-security-audit.md L6, skills/AGENTS_FRONTEND_SWAP_SAFETY_CTA.md.

Note: issue comment suggested indexer-backed pause tracking as a follow-up; this MR uses the same LCD getPairPaused path as Trade/Limit Orders for consistency and fail-closed UX when indexer is up or down.

Acceptance checklist

Item Verification Result
SwapPage queries getPairPaused for selected route pair(s) usePairPaused + swapBlacklistProbe.pairAddresses in SwapPage.tsx PASS
SwapPage disables swap submit CTA when pair paused bash scripts/with-node.sh --cwd frontend-dapp -- npm run test:run -- src/pages/SwapPage.test.tsx -t "SEC-B05" PASS
PoolPage queries getPairPaused for selected pair usePairPaused in PoolCard (PoolPage.tsx) PASS
PoolPage disables provide + withdraw CTAs when paused bash scripts/with-node.sh --cwd frontend-dapp -- npm run test:run -- src/pages/PoolPage.test.tsx -t "SEC-B05" PASS
SwapPage unit test asserts disabled swap CTA SwapPage.test.tsx describe SEC-B05 PASS
PoolPage unit tests assert disabled provide/withdraw PoolPage.test.tsx describe SEC-B05 PASS
All new tests pass in CI local Vitest + pre-push hooks (fmt/clippy/tsc/eslint) PASS

Verification checklist (third parties)

  1. bash scripts/with-node.sh --cwd frontend-dapp -- npm run test:run -- src/pages/SwapPage.test.tsx src/pages/PoolPage.test.tsx -t "SEC-B05"
  2. bash scripts/with-node.sh --cwd frontend-dapp -- npm run test:run -- src/pages/TradePage.test.tsx -t "pair is paused" (trade path regression)
  3. Optional manual (LocalTerra): pause a pair via factory SetPairPaused, open / with that pair on the route and /pool for that pair — confirm banners and disabled CTAs before signing.
  • Issue: #395
  • Invariant: L6 in docs/contracts-security-audit.md
## Summary Addresses GitLab **#395** (SEC-B05): SwapPage and PoolPage now query LCD `is_paused` for the active route pair(s) via shared hook `usePairPaused`, show pause banners, and disable swap / provide / withdraw submit CTAs before users broadcast txs that would revert on-chain under invariant **L6**. - **Swap (`/`)**: checks all pair contract addresses on the resolved swap route; CTA label **Pair is paused** (after wrap-mapper pause, before blacklist). - **Pool (`/pool`)**: checks the selected pool card pair; provide and withdraw CTAs disabled with matching banner. - Docs cross-linked: `frontend.md`, `testing.md`, `user-incident-faq.md`, `contracts-security-audit.md` L6, `skills/AGENTS_FRONTEND_SWAP_SAFETY_CTA.md`. Note: issue comment suggested indexer-backed pause tracking as a follow-up; this MR uses the same LCD `getPairPaused` path as Trade/Limit Orders for consistency and fail-closed UX when indexer is up or down. ## Acceptance checklist | Item | Verification | Result | |------|--------------|--------| | SwapPage queries `getPairPaused` for selected route pair(s) | `usePairPaused` + `swapBlacklistProbe.pairAddresses` in `SwapPage.tsx` | PASS | | SwapPage disables swap submit CTA when pair paused | `bash scripts/with-node.sh --cwd frontend-dapp -- npm run test:run -- src/pages/SwapPage.test.tsx -t "SEC-B05"` | PASS | | PoolPage queries `getPairPaused` for selected pair | `usePairPaused` in `PoolCard` (`PoolPage.tsx`) | PASS | | PoolPage disables provide + withdraw CTAs when paused | `bash scripts/with-node.sh --cwd frontend-dapp -- npm run test:run -- src/pages/PoolPage.test.tsx -t "SEC-B05"` | PASS | | SwapPage unit test asserts disabled swap CTA | `SwapPage.test.tsx` describe `SEC-B05` | PASS | | PoolPage unit tests assert disabled provide/withdraw | `PoolPage.test.tsx` describe `SEC-B05` | PASS | | All new tests pass in CI | local Vitest + pre-push hooks (fmt/clippy/tsc/eslint) | PASS | ## Verification checklist (third parties) 1. `bash scripts/with-node.sh --cwd frontend-dapp -- npm run test:run -- src/pages/SwapPage.test.tsx src/pages/PoolPage.test.tsx -t "SEC-B05"` 2. `bash scripts/with-node.sh --cwd frontend-dapp -- npm run test:run -- src/pages/TradePage.test.tsx -t "pair is paused"` (trade path regression) 3. Optional manual (LocalTerra): pause a pair via factory `SetPairPaused`, open `/` with that pair on the route and `/pool` for that pair — confirm banners and disabled CTAs before signing. ## Related - Issue: #395 - Invariant: L6 in `docs/contracts-security-audit.md`
PlasticDigits commented 2026-06-19 11:02:39 +00:00 (Migrated from gitlab.com)

Security review — MR !925

Commit reviewed: bcf9872536f8f0666d546e396e62fe2fba3a247f

Scope: Frontend pause gating for Swap (/) and Pool (/pool) via new shared hook usePairPaused, LCD getPairPaused / is_paused queries, disabled submit CTAs + incident banners, and SEC-B05 unit tests. Docs/skills cross-links only.

Outcome: FINDINGS: 0 medium+

Inline threads: None (no medium+ findings to anchor on the diff).


Method

  • Diff reviewed end-to-end (usePairPaused.ts, SwapPage.tsx, PoolPage.tsx, tests, docs).
  • Traced attacker-controlled inputs to sinks: pair addresses originate from factory/indexer pair lists and route resolution (swapBlacklistProbe.pairAddresses), filtered with startsWith('terra1') before LCD queryContract calls — same pattern as existing Trade/Limit pause paths.
  • Verified on-chain enforcement (L6): assert_not_paused gates Receive (swap / LP withdraw via CW20 Send), ProvideLiquidity, and limit-order withdrawal paths; paused txs revert regardless of UI state (smartcontracts/tests test_paused_withdraw_still_works).

Notes (below medium threshold)

Area Assessment
LCD query failure / stale cache isPaused is true only when data.paused === true (fail-open on error), matching pre-existing TradePage behavior. Worst case: user broadcasts a tx that reverts on-chain and pays gas — no fund-loss or authz bypass introduced by this MR.
UI-only CTA disable Submit handlers do not re-check isPairPaused; devtools bypass is possible but chain rejects paused executes.
Multihop pair resolution gaps If a route hop cannot be matched to a local pair address, pause query may not cover that hop; on-chain pause still blocks execution.
XSS / SSRF / secrets Banner copy and FAQ link are constants (USER_INCIDENT_FAQ_HREF); no new user-controlled HTML or logging. LCD queries target configured TERRA_LCD_URL with bech32 addresses — unchanged trust model.

Security review: no medium+ findings on this diff. This MR is a defensive UX alignment with existing Trade/Limit pause handling; authoritative controls remain on-chain.

## Security review — MR !925 **Commit reviewed:** `bcf9872536f8f0666d546e396e62fe2fba3a247f` **Scope:** Frontend pause gating for Swap (`/`) and Pool (`/pool`) via new shared hook `usePairPaused`, LCD `getPairPaused` / `is_paused` queries, disabled submit CTAs + incident banners, and SEC-B05 unit tests. Docs/skills cross-links only. **Outcome:** `FINDINGS: 0` medium+ **Inline threads:** None (no medium+ findings to anchor on the diff). --- ### Method - Diff reviewed end-to-end (`usePairPaused.ts`, `SwapPage.tsx`, `PoolPage.tsx`, tests, docs). - Traced attacker-controlled inputs to sinks: pair addresses originate from factory/indexer pair lists and route resolution (`swapBlacklistProbe.pairAddresses`), filtered with `startsWith('terra1')` before LCD `queryContract` calls — same pattern as existing Trade/Limit pause paths. - Verified on-chain enforcement (**L6**): `assert_not_paused` gates `Receive` (swap / LP withdraw via CW20 Send), `ProvideLiquidity`, and limit-order withdrawal paths; paused txs revert regardless of UI state (`smartcontracts/tests` `test_paused_withdraw_still_works`). ### Notes (below medium threshold) | Area | Assessment | |------|------------| | LCD query failure / stale cache | `isPaused` is true only when `data.paused === true` (fail-open on error), matching pre-existing `TradePage` behavior. Worst case: user broadcasts a tx that reverts on-chain and pays gas — no fund-loss or authz bypass introduced by this MR. | | UI-only CTA disable | Submit handlers do not re-check `isPairPaused`; devtools bypass is possible but chain rejects paused executes. | | Multihop pair resolution gaps | If a route hop cannot be matched to a local pair address, pause query may not cover that hop; on-chain pause still blocks execution. | | XSS / SSRF / secrets | Banner copy and FAQ link are constants (`USER_INCIDENT_FAQ_HREF`); no new user-controlled HTML or logging. LCD queries target configured `TERRA_LCD_URL` with bech32 addresses — unchanged trust model. | **Security review: no medium+ findings on this diff.** This MR is a defensive UX alignment with existing Trade/Limit pause handling; authoritative controls remain on-chain.
PlasticDigits (Migrated from gitlab.com) merged commit d487a0cd94 into main 2026-06-19 12:45:50 +00:00
PlasticDigits commented 2026-06-19 12:45:51 +00:00 (Migrated from gitlab.com)

mentioned in commit d487a0cd94

mentioned in commit d487a0cd949ed7ca3d95f46d9e99d6eecc69f62d
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!925
No description provided.