OE-3 UI: Limit Ladder — No crossing-order validation; bids above market and asks below market are accepted and placed on-chain #297

Closed
opened 2026-06-03 16:38:40 +00:00 by totdking · 18 comments
totdking commented 2026-06-03 16:38:40 +00:00 (Migrated from gitlab.com)
No description provided.
totdking commented 2026-06-03 16:57:02 +00:00 (Migrated from gitlab.com)

Found during: OE-3 checklist testing — Limit ladder multiple order placement

Summary: The limit ladder panel places orders without any crossing-price validation. Bids priced above the current market and asks priced below the current market are submitted, broadcast, and confirmed on-chain with no warning or block. This is a correctness and safety failure — crossing orders are economically equivalent to market orders but bypass all market-order UX safeguards (slippage tolerance, min-received display, price-impact warning).


Observed behavior

On the CORAL/EMBER pair (market price floating ~0.8–1.1):

Ladder type Price range set Outcome
BID ladder 2 → 10 (above market) All 5 rungs placed and confirmed on-chain
ASK ladder 0.1 → 0.5 (below market) All 5 rungs placed and confirmed on-chain

Both ladders transacted successfully with no warning, no block, and no user disclosure.


Reproduction steps

  1. Navigate to /limits or /trade/:pairAddr
  2. Connect Keplr wallet (funded test account)
  3. Open the Limit Ladder panel on a pair with a visible market price (e.g. CORAL/EMBER, ~0.8–1.1)
  4. Set a BID ladder with start price above market (e.g. 2) and end price well above market (e.g. 10)
  5. Submit — observe: all orders are placed with no crossing warning
  6. Repeat with an ASK ladder with start price below market (e.g. 0.1) and end price below market (e.g. 0.5)
  7. Submit — observe: all orders are placed with no crossing warning

Expected behavior

  • Ladder rungs that would cross the opposite best price should be blocked or warned before broadcast
  • At minimum, a confirmation dialog or inline warning should state "X of Y rungs will cross the market and execute immediately as taker orders"
  • This matches the non-crossing guard already present on the single-order limit form (TradeOrderTicket.tsx)

Actual behavior

  • No crossing check is performed
  • No warning is shown
  • All rungs are broadcast and confirmed regardless of price relative to the market

Verified root cause (code read and confirmed)

LimitOrderLadderPanel.tsx contains no reference to describeLimitCrossingBlocker, bestBid, bestAsk, crossingBlocker, or any price gate beyond escrow balance and gas:

grep result: (no matches for crossing, bestBid, bestAsk, priceGate in LimitOrderLadderPanel.tsx)

Compare to TradeOrderTicket.tsx which calls describeLimitCrossingBlocker(side, price, bestBid, bestAsk) both in the submit guard (line 406) and as a crossingBlocker memo that disables the submit button (line 322). This guard was never ported to the ladder panel.

The ladder's placeGates (from useLimitLadderPlaceGates) covers escrow balance and gas only — no price-level crossing check.


Impact assessment

  • Safety: A user setting up a "wide" ladder that spans the spread will silently execute taker fills on the crossing rungs, consuming liquidity at unfavorable prices with no disclosure.
  • Trust: The single-order form warns on crossing; the ladder form does not — inconsistent safety guarantees.
  • OE-3 result: Fail — partial failures (crossing fills) are not surfaced clearly, which is an explicit OE-3 criterion.

Environment

  • Chain: localterra
  • LCD: http://localhost:1317
  • Wallet: Keplr (Terra Classic)
  • Browser: Chrome
  • Page: /limits — Limit Ladder panel
  • Pair tested: CORAL/EMBER
  • Network throttle applied: No

Severity: ~bug — crossing orders are placed silently with no UX safeguard, exposing users to unintended taker fills at arbitrary prices.

Related checklist items: OE-3

cc: @PlasticDigits

**Found during:** OE-3 checklist testing — Limit ladder multiple order placement **Summary:** The limit ladder panel places orders without any crossing-price validation. Bids priced above the current market and asks priced below the current market are submitted, broadcast, and confirmed on-chain with no warning or block. This is a correctness and safety failure — crossing orders are economically equivalent to market orders but bypass all market-order UX safeguards (slippage tolerance, min-received display, price-impact warning). --- ### Observed behavior On the CORAL/EMBER pair (market price floating \~0.8–1.1): | Ladder type | Price range set | Outcome | |-------------|-----------------|---------| | BID ladder | 2 → 10 (above market) | All 5 rungs placed and confirmed on-chain | | ASK ladder | 0.1 → 0.5 (below market) | All 5 rungs placed and confirmed on-chain | Both ladders transacted successfully with no warning, no block, and no user disclosure. --- ### Reproduction steps 1. Navigate to `/limits` or `/trade/:pairAddr` 2. Connect Keplr wallet (funded test account) 3. Open the Limit Ladder panel on a pair with a visible market price (e.g. CORAL/EMBER, \~0.8–1.1) 4. Set a **BID** ladder with start price above market (e.g. 2) and end price well above market (e.g. 10) 5. Submit — observe: all orders are placed with no crossing warning 6. Repeat with an **ASK** ladder with start price below market (e.g. 0.1) and end price below market (e.g. 0.5) 7. Submit — observe: all orders are placed with no crossing warning --- ### Expected behavior - Ladder rungs that would cross the opposite best price should be blocked or warned before broadcast - At minimum, a confirmation dialog or inline warning should state "X of Y rungs will cross the market and execute immediately as taker orders" - This matches the non-crossing guard already present on the single-order limit form (`TradeOrderTicket.tsx`) --- ### Actual behavior - No crossing check is performed - No warning is shown - All rungs are broadcast and confirmed regardless of price relative to the market --- ### Verified root cause (code read and confirmed) `LimitOrderLadderPanel.tsx` contains no reference to `describeLimitCrossingBlocker`, `bestBid`, `bestAsk`, `crossingBlocker`, or any price gate beyond escrow balance and gas: ``` grep result: (no matches for crossing, bestBid, bestAsk, priceGate in LimitOrderLadderPanel.tsx) ``` Compare to `TradeOrderTicket.tsx` which calls `describeLimitCrossingBlocker(side, price, bestBid, bestAsk)` both in the submit guard (line 406) and as a `crossingBlocker` memo that disables the submit button (line 322). This guard was never ported to the ladder panel. The ladder's `placeGates` (from `useLimitLadderPlaceGates`) covers escrow balance and gas only — no price-level crossing check. --- ### Impact assessment - **Safety:** A user setting up a "wide" ladder that spans the spread will silently execute taker fills on the crossing rungs, consuming liquidity at unfavorable prices with no disclosure. - **Trust:** The single-order form warns on crossing; the ladder form does not — inconsistent safety guarantees. - **OE-3 result:** Fail — partial failures (crossing fills) are not surfaced clearly, which is an explicit OE-3 criterion. --- ### Environment - Chain: localterra - LCD: [http://localhost:1317](http://localhost:1317) - Wallet: Keplr (Terra Classic) - Browser: Chrome - Page: `/limits` — Limit Ladder panel - Pair tested: CORAL/EMBER - Network throttle applied: No --- **Severity:** ~bug — crossing orders are placed silently with no UX safeguard, exposing users to unintended taker fills at arbitrary prices. **Related checklist items:** OE-3 cc: @PlasticDigits
totdking commented 2026-06-03 17:28:40 +00:00 (Migrated from gitlab.com)

mentioned in issue #291

mentioned in issue #291
Brouie commented 2026-06-04 07:09:22 +00:00 (Migrated from gitlab.com)

Confirmed, and I checked the contract side so the fix surface is clear (frontend-only). LimitOrderLadderPanel.tsx has zero crossing logic — it imports none of describeLimitCrossingBlocker / bestBid / bestAsk (grep of the panel + useLimitLadderPlaceGates/useLimitLadderPlacementPlan is clean); its gates only cover escrow + gas, unlike TradeOrderTicket.tsx (the single-order form calls describeLimitCrossingBlocker(side, price, bestBid, bestAsk) at the submit guard + as a crossingBlocker memo).

Key point: I confirmed the pair contract accepts crossing limit orders by design (placement doesn't auto-match or reject crossing prices — they just rest, then fill on the next swap). So this is purely a frontend guard gap — the fix is to port the same describeLimitCrossingBlocker warning/block from TradeOrderTicket into the ladder panel; no contract change. Real, medium (crossing rungs execute as taker fills with no slippage UX). @totdking

Confirmed, and I checked the contract side so the fix surface is clear (frontend-only). `LimitOrderLadderPanel.tsx` has zero crossing logic — it imports none of `describeLimitCrossingBlocker` / `bestBid` / `bestAsk` (grep of the panel + `useLimitLadderPlaceGates`/`useLimitLadderPlacementPlan` is clean); its gates only cover escrow + gas, unlike `TradeOrderTicket.tsx` (the single-order form calls `describeLimitCrossingBlocker(side, price, bestBid, bestAsk)` at the submit guard + as a `crossingBlocker` memo). Key point: I confirmed the **pair contract accepts crossing limit orders by design** (placement doesn't auto-match or reject crossing prices — they just rest, then fill on the next swap). So this is purely a **frontend guard gap** — the fix is to port the same `describeLimitCrossingBlocker` warning/block from `TradeOrderTicket` into the ladder panel; no contract change. Real, medium (crossing rungs execute as taker fills with no slippage UX). @totdking
Brouie commented 2026-06-05 03:21:48 +00:00 (Migrated from gitlab.com)

mentioned in merge request !757

mentioned in merge request !757
Brouie commented 2026-06-05 03:23:13 +00:00 (Migrated from gitlab.com)

Fixed in MR !757. Ported the non-crossing guard the single-order form already uses (describeLimitCrossingBlocker) into the ladder: it checks each rung against best bid/ask, disables submit + shows an inline warning when any rung crosses the market, and hard-guards in the mutation. The contract accepts crossing by design, so this is a frontend guard, same as the single-order path. Browser check is yours. @PlasticDigits

Fixed in MR !757. Ported the non-crossing guard the single-order form already uses (describeLimitCrossingBlocker) into the ladder: it checks each rung against best bid/ask, disables submit + shows an inline warning when any rung crosses the market, and hard-guards in the mutation. The contract accepts crossing by design, so this is a frontend guard, same as the single-order path. Browser check is yours. @PlasticDigits
PlasticDigits commented 2026-06-05 03:24:33 +00:00 (Migrated from gitlab.com)

mentioned in commit 6e42a5b6f6

mentioned in commit 6e42a5b6f6a8a5b91735b5ad60d68168450254b0
ghost1 commented 2026-06-05 04:40:24 +00:00 (Migrated from gitlab.com)

mentioned in commit 25f8b8faa7

mentioned in commit 25f8b8faa7cfaf9644639e6070a429a5ca019859
PlasticDigits commented 2026-06-05 04:40:59 +00:00 (Migrated from gitlab.com)

mentioned in merge request !767

mentioned in merge request !767
PlasticDigits commented 2026-06-05 04:41:14 +00:00 (Migrated from gitlab.com)

Verification pass — GitLab #297 (limit ladder crossing guard)

Verified the fix from MR !757 on main. Opened follow-up MR !767 with docs cross-links + regression tests.

Results

Check Result Evidence
describeLimitCrossingBlocker wired in LimitOrderLadderPanel PASS imports + ladderCrossingGate memo + mutation hard-guard
Warning: "N of M rungs will cross the market…" PASS Vitest LimitOrderLadderPanel.crossing.test.tsx
Submit disabled on crossing BID (2→10) / ASK (0.1→0.5) PASS same Vitest file
Non-crossing default ladder (0.95→1.05) enabled PASS same Vitest file
limitOrderNonCrossing unit tests PASS 4/4
tsc -b PASS clean
Docs/skills invariant cross-links PASS MR !767
Manual browser (LocalTerra + CORAL/EMBER) FAIL deploy-dex-local.sh fails at pair creation: Pair creation requires 100000000 uluna attached after fresh make reset (×2). Component tests cover the UI paths.

Follow-ups

  • Fix LocalTerra deploy pair-creation fee attachment so full-stack QA can run again.
  • Optional Playwright crossing-guard case once deploy is green.
## Verification pass — GitLab #297 (limit ladder crossing guard) Verified the fix from MR !757 on `main`. Opened follow-up MR !767 with docs cross-links + regression tests. ### Results | Check | Result | Evidence | |-------|--------|----------| | `describeLimitCrossingBlocker` wired in `LimitOrderLadderPanel` | **PASS** | imports + `ladderCrossingGate` memo + mutation hard-guard | | Warning: "N of M rungs will cross the market…" | **PASS** | Vitest `LimitOrderLadderPanel.crossing.test.tsx` | | Submit disabled on crossing BID (2→10) / ASK (0.1→0.5) | **PASS** | same Vitest file | | Non-crossing default ladder (0.95→1.05) enabled | **PASS** | same Vitest file | | `limitOrderNonCrossing` unit tests | **PASS** | 4/4 | | `tsc -b` | **PASS** | clean | | Docs/skills invariant cross-links | **PASS** | MR !767 | | Manual browser (LocalTerra + CORAL/EMBER) | **FAIL** | `deploy-dex-local.sh` fails at pair creation: `Pair creation requires 100000000 uluna attached` after fresh `make reset` (×2). Component tests cover the UI paths. | ### Follow-ups - Fix LocalTerra deploy pair-creation fee attachment so full-stack QA can run again. - Optional Playwright crossing-guard case once deploy is green.
PlasticDigits commented 2026-06-05 04:56:37 +00:00 (Migrated from gitlab.com)

mentioned in commit f9bf77b7cd

mentioned in commit f9bf77b7cd6ada2d63da43b1ab2b06dd0f3cb7f0
PlasticDigits commented 2026-06-05 11:03:56 +00:00 (Migrated from gitlab.com)

Verification — GitLab #297 (limit ladder crossing guard)

Re-verified on main after MR !757 (implementation) and MR !767 (docs/tests). Branch: cursor/gitlab-issue-verification-a8d9 @ c0d8ad6.

Acceptance criteria

Item Result How verified
describeLimitCrossingBlocker wired in LimitOrderLadderPanel (imports, per-rung memo, submit disable) PASS grep + read LimitOrderLadderPanel.tsx (ladderCrossingGate, useTradeBestBookPrices, data-testid="ladder-crossing-guard")
Inline warning: N of M rungs will cross the market… PASS Vitest LimitOrderLadderPanel.crossing.test.tsx
BID ladder 2→10 blocked (issue repro) PASS Vitest: blocks bid ladder when rungs cross best ask
ASK ladder 0.1→0.5 blocked (issue repro) PASS Vitest: blocks ask ladder when rungs cross best bid
Non-crossing ladder (0.95→1.05 default) submit enabled PASS Vitest: allows non-crossing bid ladder within spread
Mutation hard-guard before broadcast PASS mutationFn throws if any rung crosses (describeLimitCrossingBlocker loop)
Parity with TradeOrderTicket guard PASS Same helper + useTradeBestBookPrices; contract still accepts crossing on-chain (#152) — client-only UX
limitOrderNonCrossing unit tests PASS npm test -- limitOrderNonCrossing → 4/4
Typecheck PASS npx tsc -b (frontend-dapp)
Docs / skill invariant #13 PASS skills/AGENTS_LIMIT_ORDER_BATCH_LADDER.md, docs/limit-orders.md, docs/frontend.md cross-link #297
Manual Chrome on LocalTerra + CORAL/EMBER (Keplr) FAIL No .env.local / wasm artifacts on this VM; full make setup-cloud-localterra not run (15+ min build+deploy). Component Vitest exercises the same UI paths as the issue repro steps.

Commands

source ~/.nvm/nvm.sh && nvm use
export PATH="$(dirname "$(nvm which node)"):$PATH"
cd frontend-dapp
npm test -- LimitOrderLadderPanel.crossing limitOrderNonCrossing   # 7/7 passed
npx tsc -b                                                        # clean

Outcome

All product acceptance items for the crossing guard are met on main. Closing as verified; manual stack QA remains optional once LocalTerra deploy is provisioned.

Follow-up (non-blocking): optional Playwright case for ladder-crossing-guard when E2E deploy env is available.

## Verification — GitLab [#297](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/297) (limit ladder crossing guard) Re-verified on `main` after MR !757 (implementation) and MR !767 (docs/tests). Branch: `cursor/gitlab-issue-verification-a8d9` @ `c0d8ad6`. ### Acceptance criteria | Item | Result | How verified | |------|--------|----------------| | `describeLimitCrossingBlocker` wired in `LimitOrderLadderPanel` (imports, per-rung memo, submit disable) | **PASS** | `grep` + read `LimitOrderLadderPanel.tsx` (`ladderCrossingGate`, `useTradeBestBookPrices`, `data-testid="ladder-crossing-guard"`) | | Inline warning: `N of M rungs will cross the market…` | **PASS** | Vitest `LimitOrderLadderPanel.crossing.test.tsx` | | BID ladder 2→10 blocked (issue repro) | **PASS** | Vitest: `blocks bid ladder when rungs cross best ask` | | ASK ladder 0.1→0.5 blocked (issue repro) | **PASS** | Vitest: `blocks ask ladder when rungs cross best bid` | | Non-crossing ladder (0.95→1.05 default) submit enabled | **PASS** | Vitest: `allows non-crossing bid ladder within spread` | | Mutation hard-guard before broadcast | **PASS** | `mutationFn` throws if any rung crosses (`describeLimitCrossingBlocker` loop) | | Parity with `TradeOrderTicket` guard | **PASS** | Same helper + `useTradeBestBookPrices`; contract still accepts crossing on-chain ([#152](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/152)) — client-only UX | | `limitOrderNonCrossing` unit tests | **PASS** | `npm test -- limitOrderNonCrossing` → 4/4 | | Typecheck | **PASS** | `npx tsc -b` (frontend-dapp) | | Docs / skill invariant #13 | **PASS** | `skills/AGENTS_LIMIT_ORDER_BATCH_LADDER.md`, `docs/limit-orders.md`, `docs/frontend.md` cross-link #297 | | Manual Chrome on LocalTerra + CORAL/EMBER (Keplr) | **FAIL** | No `.env.local` / wasm artifacts on this VM; full `make setup-cloud-localterra` not run (15+ min build+deploy). Component Vitest exercises the same UI paths as the issue repro steps. | ### Commands ```bash source ~/.nvm/nvm.sh && nvm use export PATH="$(dirname "$(nvm which node)"):$PATH" cd frontend-dapp npm test -- LimitOrderLadderPanel.crossing limitOrderNonCrossing # 7/7 passed npx tsc -b # clean ``` ### Outcome All product acceptance items for the crossing guard are **met** on `main`. Closing as verified; manual stack QA remains optional once LocalTerra deploy is provisioned. **Follow-up (non-blocking):** optional Playwright case for `ladder-crossing-guard` when E2E deploy env is available.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-05 11:04:00 +00:00
totdking commented 2026-06-16 10:50:55 +00:00 (Migrated from gitlab.com)

Verification

This issue still persists, the ladder section of limits allow for bid tx above current market price and ask tx below current market price

Screen record

Screen Recording 2026-06-16 at 11.45.01.mov

This holds the following parameters:

  • Pair name: Ember/coral
  • current price ratio: 0.969780 CORAL/EMBER

Bids allow for large tx above the current price which is prohibited in the normal limit order. But the ask part of the ladder does not go through.

cc: @PlasticDigits

### Verification This issue still persists, the ladder section of limits allow for bid tx above current market price and ask tx below current market price ### Screen record ![Screen Recording 2026-06-16 at 11.45.01.mov](/uploads/c43b6ab0ddd81aa75f8ec4d3ff512108/Screen_Recording_2026-06-16_at_11.45.01.mov) This holds the following parameters: * Pair name: Ember/coral * current price ratio: 0.969780 CORAL/EMBER Bids allow for large tx above the current price which is prohibited in the normal limit order. But the ask part of the ladder does not go through. cc: @PlasticDigits
totdking commented 2026-06-16 15:15:26 +00:00 (Migrated from gitlab.com)

mentioned in issue #385

mentioned in issue #385
PlasticDigits commented 2026-06-16 15:34:17 +00:00 (Migrated from gitlab.com)

mentioned in merge request !914

mentioned in merge request !914
PlasticDigits commented 2026-08-17 10:26:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #546

mentioned in issue #546
PlasticDigits commented 2026-08-24 03:15:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #617

mentioned in issue #617
PlasticDigits commented 2026-08-24 03:15:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #619

mentioned in issue #619
PlasticDigits commented 2026-09-01 08:14:37 +00:00 (Migrated from gitlab.com)

mentioned in issue #717

mentioned in issue #717
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#297
No description provided.