Security: no LocalTerra swap trace to verify route display aligns with actual submit operations [SEC-E07] #428

Closed
opened 2026-06-29 14:43:34 +00:00 by totdking · 7 comments
totdking commented 2026-06-29 14:43:34 +00:00 (Migrated from gitlab.com)
No description provided.
totdking commented 2026-06-29 14:45:42 +00:00 (Migrated from gitlab.com)

Summary

swapRouteDisplay.test.ts covers unit logic for computing the displayed path and verifies that deriveSwapSubmitRouteSource returns the correct source branch (indexer, client BFS, direct, wrap/unwrap) for each route scenario. Unit coverage is present. The checklist also requires a LocalTerra swap trace or e2e test confirming the displayed path matches the wasm operations actually submitted on-chain. No such trace or e2e test exists in docs/runbooks/, frontend-dapp/e2e/, or any script.


What Was Checked

  • frontend-dapp/src/utils/swapRouteDisplay.test.ts: computeSwapRouteDisplay, deriveSwapSubmitRouteSource, tokenPathFromSwapOperations, tokenPathForNativeSupportedRoute all unit tested. No on-chain verification.
  • docs/runbooks/ (full listing verified): blacklist-decision.md, emergency-commands.md, governance-emergency-rehearsal.md, launch-checklist.md, wasm-admin-migration.md, and others. No swap route trace or route-alignment transcript.
  • frontend-dapp/e2e/: Playwright specs exist (e.g., wrap-swap.spec.ts) but no spec asserting displayed route tokens match submitted wasm events.
  • scripts/: no LocalTerra swap script comparing UI route display to on-chain event output.

Expected (per checklist)

A LocalTerra swap trace or e2e test must confirm that for at least one multi-hop and one direct pair swap, the route shown in the UI matches the operations submitted to the chain.


Actual

Only unit tests of display logic exist. A rendering mismatch between the displayed path and the submitted operations would not be caught automatically.


Evidence

  • frontend-dapp/src/utils/swapRouteDisplay.test.ts: unit coverage present, no on-chain trace
  • docs/runbooks/: no route-alignment document found
  • frontend-dapp/e2e/: no route-alignment Playwright spec

Suggested Fix

Add a Playwright e2e test that selects a multi-hop route on the swap page, captures the displayed path tokens, submits the swap, and checks the wasm execute events for the same hop sequence. As interim evidence, a LocalTerra swap transcript attached to this issue showing the displayed route (screenshot or log) and the submitted tx JSON is sufficient until an automated test is written.


Verification Checklist

  • E2e test or LocalTerra transcript confirms displayed route matches submitted operations for a multi-hop swap
  • Trace also covers a direct pair swap
  • No duplicate or conflicting route segments appear in either display or submitted operations

Labels

security, pre-launch

Cc: @PlasticDigits

### Summary `swapRouteDisplay.test.ts` covers unit logic for computing the displayed path and verifies that `deriveSwapSubmitRouteSource` returns the correct source branch (indexer, client BFS, direct, wrap/unwrap) for each route scenario. Unit coverage is present. The checklist also requires a LocalTerra swap trace or e2e test confirming the displayed path matches the wasm operations actually submitted on-chain. No such trace or e2e test exists in `docs/runbooks/`, `frontend-dapp/e2e/`, or any script. --- ### What Was Checked - `frontend-dapp/src/utils/swapRouteDisplay.test.ts`: `computeSwapRouteDisplay`, `deriveSwapSubmitRouteSource`, `tokenPathFromSwapOperations`, `tokenPathForNativeSupportedRoute` all unit tested. No on-chain verification. - `docs/runbooks/` (full listing verified): `blacklist-decision.md`, `emergency-commands.md`, `governance-emergency-rehearsal.md`, `launch-checklist.md`, `wasm-admin-migration.md`, and others. No swap route trace or route-alignment transcript. - `frontend-dapp/e2e/`: Playwright specs exist (e.g., `wrap-swap.spec.ts`) but no spec asserting displayed route tokens match submitted wasm events. - `scripts/`: no LocalTerra swap script comparing UI route display to on-chain event output. --- ### Expected (per checklist) A LocalTerra swap trace or e2e test must confirm that for at least one multi-hop and one direct pair swap, the route shown in the UI matches the operations submitted to the chain. --- ### Actual Only unit tests of display logic exist. A rendering mismatch between the displayed path and the submitted operations would not be caught automatically. --- ### Evidence - `frontend-dapp/src/utils/swapRouteDisplay.test.ts`: unit coverage present, no on-chain trace - `docs/runbooks/`: no route-alignment document found - `frontend-dapp/e2e/`: no route-alignment Playwright spec --- ### Suggested Fix Add a Playwright e2e test that selects a multi-hop route on the swap page, captures the displayed path tokens, submits the swap, and checks the wasm execute events for the same hop sequence. As interim evidence, a LocalTerra swap transcript attached to this issue showing the displayed route (screenshot or log) and the submitted tx JSON is sufficient until an automated test is written. --- ### Verification Checklist - [ ] E2e test or LocalTerra transcript confirms displayed route matches submitted operations for a multi-hop swap - [ ] Trace also covers a direct pair swap - [ ] No duplicate or conflicting route segments appear in either display or submitted operations --- ### Labels `security`, `pre-launch` Cc: @PlasticDigits
Brouie commented 2026-06-29 15:30:18 +00:00 (Migrated from gitlab.com)

Dropped an interim LocalTerra trace here — the checklist accepts a transcript until the automated e2e lands, and that e2e is the agent:implement MR. Traced the displayed route source (indexer route/solve hops + router_operations — what the UI renders via computeSwapRouteDisplay, already unit-tested in swapRouteDisplay.test.ts) against the operations actually submitted on-chain, for both a multi-hop and a direct swap.

Multi-hop — EMBER -> RUBY (1 EMBER), tx 126A86DF, code 0:

  • displayed route (route/solve, global_v4): EMBER -> JADE -> RUBY, via pairs terra14kdhrgf (EMBER/JADE) then terra1eh2aul (JADE/RUBY); router_operations carry the same two terra_swap ops in that order.
  • submitted on-chain: exactly 2 wasm swap events — swap EMBER->JADE on terra14kdhrgf, then JADE->RUBY on terra1eh2aul. Same token sequence, same pairs, same order.
  • final return 2,963,621,042 RUBY == route/solve estimated_amount_out, to the unit.

Direct — TOPAZ -> OPAL (1 TOPAZ), tx 1B88B2CF, code 0:

  • displayed route: single hop TOPAZ -> OPAL via pair terra1cduudfszcm.
  • submitted on-chain: exactly 1 wasm swap event on terra1cduudfszcm, offer TOPAZ -> ask OPAL; return 3,052,946 == estimated_amount_out exactly.

No duplicate or conflicting route segments in either: multi-hop is exactly one swap event per displayed hop (2), direct is exactly one (1).

Two notes worth carrying into the automated e2e:

  • On this seed topology the best-execution solver arbs even directly-paired tokens: route/solve for EMBER->CORAL (which have a direct pool) returns a 3-hop EMBER->ONYX->RUBY->CORAL (~17.05B CORAL vs the direct pool's ~888k). So the e2e should pick its "direct pair" case from a pair where the direct pool is actually optimal (TOPAZ/OPAL works), or assert against whatever route/solve returns rather than assuming direct == 1 hop.
  • Hybrid hops need a per-hop min_return at submit (the #334 floor) — the raw route/solve router_operations aren't directly executable until the dapp enriches them (the #360 align step). The e2e should submit the enriched ops, which is what the dapp does.

This is the data-layer + on-chain half. The remaining piece is the automated Playwright spec that captures the rendered route line and asserts it against the submit payload — that's the agent:implement MR, and the rendered-pixel capture is the laptop/clean-host part. @PlasticDigits — interim trace attached per the checklist; the automated test stays on the agent.

Dropped an interim LocalTerra trace here — the checklist accepts a transcript until the automated e2e lands, and that e2e is the agent:implement MR. Traced the displayed route source (indexer route/solve hops + router_operations — what the UI renders via computeSwapRouteDisplay, already unit-tested in swapRouteDisplay.test.ts) against the operations actually submitted on-chain, for both a multi-hop and a direct swap. Multi-hop — EMBER -> RUBY (1 EMBER), tx 126A86DF, code 0: - displayed route (route/solve, global_v4): EMBER -> JADE -> RUBY, via pairs terra14kdhrgf (EMBER/JADE) then terra1eh2aul (JADE/RUBY); router_operations carry the same two terra_swap ops in that order. - submitted on-chain: exactly 2 wasm swap events — swap EMBER->JADE on terra14kdhrgf, then JADE->RUBY on terra1eh2aul. Same token sequence, same pairs, same order. - final return 2,963,621,042 RUBY == route/solve estimated_amount_out, to the unit. Direct — TOPAZ -> OPAL (1 TOPAZ), tx 1B88B2CF, code 0: - displayed route: single hop TOPAZ -> OPAL via pair terra1cduudfszcm. - submitted on-chain: exactly 1 wasm swap event on terra1cduudfszcm, offer TOPAZ -> ask OPAL; return 3,052,946 == estimated_amount_out exactly. No duplicate or conflicting route segments in either: multi-hop is exactly one swap event per displayed hop (2), direct is exactly one (1). Two notes worth carrying into the automated e2e: - On this seed topology the best-execution solver arbs even directly-paired tokens: route/solve for EMBER->CORAL (which have a direct pool) returns a 3-hop EMBER->ONYX->RUBY->CORAL (~17.05B CORAL vs the direct pool's ~888k). So the e2e should pick its "direct pair" case from a pair where the direct pool is actually optimal (TOPAZ/OPAL works), or assert against whatever route/solve returns rather than assuming direct == 1 hop. - Hybrid hops need a per-hop min_return at submit (the #334 floor) — the raw route/solve router_operations aren't directly executable until the dapp enriches them (the #360 align step). The e2e should submit the enriched ops, which is what the dapp does. This is the data-layer + on-chain half. The remaining piece is the automated Playwright spec that captures the rendered route line and asserts it against the submit payload — that's the agent:implement MR, and the rendered-pixel capture is the laptop/clean-host part. @PlasticDigits — interim trace attached per the checklist; the automated test stays on the agent.
totdking commented 2026-06-29 15:31:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #381

mentioned in issue #381
PlasticDigits commented 2026-06-29 16:14:37 +00:00 (Migrated from gitlab.com)

mentioned in commit 36e322bf49

mentioned in commit 36e322bf4975596de4645f4804d0abd030aa10d0
PlasticDigits commented 2026-06-29 16:14:52 +00:00 (Migrated from gitlab.com)

mentioned in merge request !963

mentioned in merge request !963
PlasticDigits commented 2026-06-29 16:22:20 +00:00 (Migrated from gitlab.com)

mentioned in commit de59858d9a

mentioned in commit de59858d9a7d4b0f16a39ede4b6a017c5245c7ad
PlasticDigits commented 2026-06-30 02:33:28 +00:00 (Migrated from gitlab.com)

Verification complete — SEC-E07 / #428

All acceptance criteria PASS. No repo changes; closing issue.

Checklist

Criterion Result Evidence
Multi-hop: displayed route matches submitted on-chain operations PASS Playwright e2e/swap-route-alignment-tx.spec.ts — multihop test (CORAL→IRON, ≥2 wasm hops); UI swap-route-summary symbols equal tx offer_asset/ask_asset sequence. Interim LocalTerra trace in @Brouie comment (EMBER→JADE→RUBY, tx 126A86DF) also satisfies checklist.
Direct pair swap trace PASS Same spec — direct dual-CW20 test (2 symbols, 1 wasm hop). Interim trace (TOPAZ→OPAL, tx 1B88B2CF) in issue comments.
No duplicate or conflicting route segments PASS assertNoDuplicateRouteSegments in e2e/helpers/route-alignment-e2e.ts runs on both display and tx paths in e2e assertions.

How verified

  1. Unit: bash scripts/with-node.sh --cwd frontend-dapp -- npm run test -- --run src/utils/swapRouteDisplay.test.ts — 5/5 passed (computeSwapRouteDisplay, deriveSwapSubmitRouteSource, path helpers).
  2. Infra: make setup-cloud-localterra (fresh deploy after stale-volume reset); LocalTerra + indexer up.
  3. E2E (automated): CI=1 bash scripts/with-node.sh --cwd frontend-dapp -- npx playwright test e2e/swap-route-alignment-tx.spec.ts --project=e2e-tx — 2 passed (54s): direct pair + multihop hybrid.
  4. Docs: Playbook at skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md § On-chain route alignment; frontend-dapp/e2e/README.md and docs/testing.md cross-link the spec.

Follow-ups (optional)

  • Trade market (/trade → Market tab) route alignment e2e is not covered by this spec (Swap / only today); consider if product wants parity.
## Verification complete — SEC-E07 / #428 All acceptance criteria **PASS**. No repo changes; closing issue. ### Checklist | Criterion | Result | Evidence | |-----------|--------|----------| | Multi-hop: displayed route matches submitted on-chain operations | **PASS** | Playwright `e2e/swap-route-alignment-tx.spec.ts` — multihop test (CORAL→IRON, ≥2 wasm hops); UI `swap-route-summary` symbols equal tx `offer_asset`/`ask_asset` sequence. Interim LocalTerra trace in @Brouie comment (EMBER→JADE→RUBY, tx 126A86DF) also satisfies checklist. | | Direct pair swap trace | **PASS** | Same spec — direct dual-CW20 test (2 symbols, 1 wasm hop). Interim trace (TOPAZ→OPAL, tx 1B88B2CF) in issue comments. | | No duplicate or conflicting route segments | **PASS** | `assertNoDuplicateRouteSegments` in `e2e/helpers/route-alignment-e2e.ts` runs on both display and tx paths in e2e assertions. | ### How verified 1. **Unit:** `bash scripts/with-node.sh --cwd frontend-dapp -- npm run test -- --run src/utils/swapRouteDisplay.test.ts` — 5/5 passed (`computeSwapRouteDisplay`, `deriveSwapSubmitRouteSource`, path helpers). 2. **Infra:** `make setup-cloud-localterra` (fresh deploy after stale-volume reset); LocalTerra + indexer up. 3. **E2E (automated):** `CI=1 bash scripts/with-node.sh --cwd frontend-dapp -- npx playwright test e2e/swap-route-alignment-tx.spec.ts --project=e2e-tx` — **2 passed** (54s): direct pair + multihop hybrid. 4. **Docs:** Playbook at `skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md` § On-chain route alignment; `frontend-dapp/e2e/README.md` and `docs/testing.md` cross-link the spec. ### Follow-ups (optional) - Trade market (`/trade` → Market tab) route alignment e2e is not covered by this spec (Swap `/` only today); consider if product wants parity.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-30 02:33:29 +00:00
Sign in to join this conversation.
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#428
No description provided.