fix(limits): book Edit preserves orderId and enables price-only update (#312) #791

Merged
PlasticDigits merged 2 commits from cursor/gitlab-issue-workflow-cb0d into main 2026-06-05 10:38:24 +00:00
PlasticDigits commented 2026-06-05 10:09:23 +00:00 (Migrated from gitlab.com)

Summary

Fixes https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/312 (follow-up to #294).

The /limits page dropped orderId when prefilling from the order book Edit action, so submit always called place_limit_order and created duplicate resting orders. This mirrors the existing /trade ticket flow:

  • buildLimitBookEditContext(draft) on book prefill (includes orderId, expiresAt, hintAfterOrderId)
  • Price-only changes submit UpdateLimitOrderPrice via useLimitOrderUpdatePriceMutation
  • Side / amount / expiry changes block with the same copy as Trade (LIMIT_EDIT_NON_PRICE_CHANGE_MESSAGE)
  • Button label switches between Update price and Place limit
  • Edit clears on pair switch and successful place/update

Acceptance checklist

Criterion Verification Result
Edit from book on /limits prefill shows “Editing order #N” npm run test -- src/pages/LimitOrdersPage.test.tsx — book Edit prefills ticket with orderId PASS
Price-only change submits UpdateLimitOrderPrice, not place_limit_order Same file — price-only amend on /limits submits UpdateLimitOrderPrice PASS
orderId preserved through prefill; no duplicate on price amend Unit test asserts updateLimitOrderPrice called with order id 7; edit context shows #7 PASS
Changing amount/side/expiry matches Trade ticket policy Same file — blocks silent duplicate when side changes PASS
Tests cover Limits prefill includes orderId LimitOrdersPage.test.tsx (4 new cases) PASS
make test-frontend green make test-frontend (832 tests) PASS
Manual: /limits book Edit → price amend → single order id on LCD Requires LocalTerra + wallet QA SKIP

Verification for third parties

export PATH="$HOME/.nvm/versions/node/$(cat .nvmrc)/bin:$PATH"
make test-frontend
# targeted:
cd frontend-dapp && npm run test -- src/pages/LimitOrdersPage.test.tsx

Manual (LocalTerra): open /limits → select pair → Edit own book row → change price only → Update price → confirm same order_id on LCD/indexer.

Files touched

  • frontend-dapp/src/pages/LimitOrdersPage.tsx — edit context + update-price submit path
  • frontend-dapp/src/pages/LimitOrdersPage.test.tsx — regression tests

Reference implementation unchanged: TradeOrderTicket.tsx, limitOrderPriceEdit.ts.


Note

Medium Risk
Changes on-chain submit behavior for limit edits (place vs update-price) and wallet tx paths; scope is limited to the limits page UI with solid test coverage.

Overview
Fixes a bug where Edit on the /limits order book only prefilled side/price/amount and always submitted Place limit, creating duplicate resting orders.

LimitOrdersPage now mirrors the /trade ticket flow: book prefill builds edit context (orderId, expiry, insert hint), shows an “Editing order #N” banner, and routes price-only changes through UpdateLimitOrderPrice via useLimitOrderUpdatePriceMutation (separate gas estimate and success/error UI). Side, amount, or expiry changes disable submit and show the same non-price policy copy as Trade; Place limit is blocked while edit context is active. Edit state clears on pair switch and after successful place or update.

LimitOrdersPage.test.tsx adds four regression tests (prefill, price-only update tx, side-change guard, paused pair disables Edit).

Reviewed by Cursor Bugbot for commit f9b5b8287f. Bugbot is set up for automated code reviews on this repo. Configure here.

## Summary Fixes https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/312 (follow-up to #294). The `/limits` page dropped `orderId` when prefilling from the order book **Edit** action, so submit always called `place_limit_order` and created duplicate resting orders. This mirrors the existing `/trade` ticket flow: - `buildLimitBookEditContext(draft)` on book prefill (includes `orderId`, `expiresAt`, `hintAfterOrderId`) - Price-only changes submit `UpdateLimitOrderPrice` via `useLimitOrderUpdatePriceMutation` - Side / amount / expiry changes block with the same copy as Trade (`LIMIT_EDIT_NON_PRICE_CHANGE_MESSAGE`) - Button label switches between **Update price** and **Place limit** - Edit clears on pair switch and successful place/update ## Acceptance checklist | Criterion | Verification | Result | |-----------|--------------|--------| | Edit from book on `/limits` prefill shows “Editing order #N” | `npm run test -- src/pages/LimitOrdersPage.test.tsx` — *book Edit prefills ticket with orderId* | PASS | | Price-only change submits `UpdateLimitOrderPrice`, not `place_limit_order` | Same file — *price-only amend on /limits submits UpdateLimitOrderPrice* | PASS | | `orderId` preserved through prefill; no duplicate on price amend | Unit test asserts `updateLimitOrderPrice` called with order id `7`; edit context shows `#7` | PASS | | Changing amount/side/expiry matches Trade ticket policy | Same file — *blocks silent duplicate when side changes* | PASS | | Tests cover Limits prefill includes `orderId` | `LimitOrdersPage.test.tsx` (4 new cases) | PASS | | `make test-frontend` green | `make test-frontend` (832 tests) | PASS | | Manual: `/limits` book Edit → price amend → single order id on LCD | Requires LocalTerra + wallet QA | SKIP | ## Verification for third parties ```bash export PATH="$HOME/.nvm/versions/node/$(cat .nvmrc)/bin:$PATH" make test-frontend # targeted: cd frontend-dapp && npm run test -- src/pages/LimitOrdersPage.test.tsx ``` Manual (LocalTerra): open `/limits` → select pair → **Edit** own book row → change price only → **Update price** → confirm same `order_id` on LCD/indexer. ## Files touched - `frontend-dapp/src/pages/LimitOrdersPage.tsx` — edit context + update-price submit path - `frontend-dapp/src/pages/LimitOrdersPage.test.tsx` — regression tests Reference implementation unchanged: `TradeOrderTicket.tsx`, `limitOrderPriceEdit.ts`. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes on-chain submit behavior for limit edits (place vs update-price) and wallet tx paths; scope is limited to the limits page UI with solid test coverage. > > **Overview** > Fixes a bug where **Edit** on the `/limits` order book only prefilled side/price/amount and always submitted **Place limit**, creating duplicate resting orders. > > **LimitOrdersPage** now mirrors the `/trade` ticket flow: book prefill builds **edit context** (`orderId`, expiry, insert hint), shows an “Editing order #N” banner, and routes **price-only** changes through **`UpdateLimitOrderPrice`** via `useLimitOrderUpdatePriceMutation` (separate gas estimate and success/error UI). Side, amount, or expiry changes disable submit and show the same non-price policy copy as Trade; **Place limit** is blocked while edit context is active. Edit state clears on pair switch and after successful place or update. > > **LimitOrdersPage.test.tsx** adds four regression tests (prefill, price-only update tx, side-change guard, paused pair disables Edit). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f9b5b8287f9d52c9ab91edd0d62b92baddbc22f2. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
ghost1 commented 2026-06-05 10:09:30 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-05 10:09:31 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-05 10:09:31 +00:00 (Migrated from gitlab.com)

mentioned in issue #312

mentioned in issue #312
ghost1 commented 2026-06-05 10:09:38 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-05 10:10:55 +00:00 (Migrated from gitlab.com)

Security review

Commit reviewed: 0a1810e9591052d74565187abe5ebcb415e3e542
Scope: frontend-dapp/src/pages/LimitOrdersPage.tsx, frontend-dapp/src/pages/LimitOrdersPage.test.tsx — mirrors existing /trade limit-order edit flow (buildLimitBookEditContext, isPriceOnlyLimitEdit, useLimitOrderUpdatePriceMutation) onto /limits.

Outcome: FINDINGS: 0 medium+

Analysis

Area Result
Authz / IDOR orderId for UpdateLimitOrderPrice comes only from book Edit on rows where order.owner === walletAddress (OrderBookPanel). On-chain execute_update_limit_order_price enforces o.owner == info.sender → Unauthorized otherwise. UI state tampering cannot amend another user's order.
Duplicate / unintended placement While editContext is set, placeLimitCombinedOk requires !editContext; non–price-only field changes set editNonPriceChanged and disable submit. Aligns with TradeOrderTicket.tsx.
Injection / XSS orderId rendered as a React number node; price validated by existing place gates and contract Decimal checks. No new HTML sinks or dynamic code paths.
Pause / governance (L6) Paused-pair guard preserved; new test asserts Edit disabled when paused.
Secrets / SSRF / deps No changes in these areas.

Prior MR security threads: none to re-report.

Inline threads: none (no medium+ findings).

## Security review **Commit reviewed:** `0a1810e9591052d74565187abe5ebcb415e3e542` **Scope:** `frontend-dapp/src/pages/LimitOrdersPage.tsx`, `frontend-dapp/src/pages/LimitOrdersPage.test.tsx` — mirrors existing `/trade` limit-order edit flow (`buildLimitBookEditContext`, `isPriceOnlyLimitEdit`, `useLimitOrderUpdatePriceMutation`) onto `/limits`. **Outcome:** `FINDINGS: 0` medium+ ### Analysis | Area | Result | |------|--------| | **Authz / IDOR** | `orderId` for `UpdateLimitOrderPrice` comes only from book **Edit** on rows where `order.owner === walletAddress` (`OrderBookPanel`). On-chain `execute_update_limit_order_price` enforces `o.owner == info.sender` → `Unauthorized` otherwise. UI state tampering cannot amend another user's order. | | **Duplicate / unintended placement** | While `editContext` is set, `placeLimitCombinedOk` requires `!editContext`; non–price-only field changes set `editNonPriceChanged` and disable submit. Aligns with `TradeOrderTicket.tsx`. | | **Injection / XSS** | `orderId` rendered as a React number node; `price` validated by existing place gates and contract `Decimal` checks. No new HTML sinks or dynamic code paths. | | **Pause / governance (L6)** | Paused-pair guard preserved; new test asserts Edit disabled when paused. | | **Secrets / SSRF / deps** | No changes in these areas. | Prior MR security threads: none to re-report. **Inline threads:** none (no medium+ findings).
ghost1 commented 2026-06-05 10:11:44 +00:00 (Migrated from gitlab.com)

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.

Reviewed by Cursor Bugbot for commit 0a1810e959. Configure here.

<!-- BUGBOT_REVIEW --> Cursor Bugbot has reviewed your changes and found 1 potential issue. <!-- BUGBOT_FIX_ALL --> <a href="https://cursor.com/open?link=eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9BTExfSU5fQ1VSU09SIiwiZGF0YSI6eyJyZWRpc0tleSI6ImJ1Z2JvdC1tdWx0aTo4MTE1ZTZiNS02OGFjLTRiMjktOTBmNC03MDBmYjY3NjZiYWMiLCJlbmNyeXB0aW9uS2V5IjoiWFdZWE84OExKbHoySXNMTGFqT0xnWVNKV0VzTllTRlJfWU12aDlMQmx6byIsImJyYW5jaCI6ImN1cnNvci9naXRsYWItaXNzdWUtd29ya2Zsb3ctY2IwZCIsInJlcG9Pd25lciI6IlBsYXN0aWNEaWdpdHMiLCJyZXBvTmFtZSI6ImNsOHktZGV4LXRlcnJhY2xhc3NpYyJ9fQ" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/fix-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/fix-in-cursor-light.png"><img alt="Fix All in Cursor" width="115" height="28" src="https://cursor.com/assets/images/fix-in-cursor-dark.png"></picture></a> <!-- /BUGBOT_FIX_ALL --> <!-- BUGBOT_AUTOFIX_REVIEW_FOOTNOTE_BEGIN --> <sup>Bugbot Autofix is [ON](https://www.cursor.com/dashboard/bugbot). A cloud agent has been kicked off to fix the reported issue. <!-- BUGBOT_AUTOFIX_AGENT_LINK --></sup> <!-- BUGBOT_AUTOFIX_REVIEW_FOOTNOTE_END --> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0a1810e9591052d74565187abe5ebcb415e3e542. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
ghost1 commented 2026-06-05 10:11:45 +00:00 (Migrated from gitlab.com)

Edit prefill omits expiry sync

Medium Severity

Book Edit stores the order’s expiresAt in editContext but onPrefillLimitTicketFromBook never updates the form’s expiresAt. isPriceOnlyLimitEdit and editNonPriceChanged compare those values, so a stale ticket expiry can block “Update price” or show a false non-price change even when only price was edited.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0a1810e959. Configure here.

### Edit prefill omits expiry sync **Medium Severity** <!-- DESCRIPTION START --> Book Edit stores the order’s `expiresAt` in `editContext` but `onPrefillLimitTicketFromBook` never updates the form’s `expiresAt`. `isPriceOnlyLimitEdit` and `editNonPriceChanged` compare those values, so a stale ticket expiry can block “Update price” or show a false non-price change even when only price was edited. <!-- DESCRIPTION END --> <!-- BUGBOT_BUG_ID: bb030a0b-3a65-4dce-810b-6a2b2110ba06 --> <!-- LOCATIONS START frontend-dapp/src/pages/LimitOrdersPage.tsx#L222-L233 LOCATIONS END --> <div><a href="https://cursor.com/open?link=eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9JTl9DVVJTT1IiLCJkYXRhIjp7InJlZGlzS2V5IjoiYnVnYm90OjZjNDk5MTgxLWRhN2QtNDdjZi1hMmJjLTRhNTA0NWVmNTJhNSIsImVuY3J5cHRpb25LZXkiOiJTVjBaZFhzV1lMN0pUV1VDOEhHY0o2ZERWRkpUZHMwYTRsbms1dHVheDQ0IiwiYnJhbmNoIjoiY3Vyc29yL2dpdGxhYi1pc3N1ZS13b3JrZmxvdy1jYjBkIiwicmVwb093bmVyIjoiUGxhc3RpY0RpZ2l0cyIsInJlcG9OYW1lIjoiY2w4eS1kZXgtdGVycmFjbGFzc2ljIn19" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/fix-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/fix-in-cursor-light.png"><img alt="Fix in Cursor" width="115" height="28" src="https://cursor.com/assets/images/fix-in-cursor-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/agents?link=eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9JTl9XRUIiLCJkYXRhIjp7InJlZGlzS2V5IjoiYnVnYm90OjZjNDk5MTgxLWRhN2QtNDdjZi1hMmJjLTRhNTA0NWVmNTJhNSIsImVuY3J5cHRpb25LZXkiOiJTVjBaZFhzV1lMN0pUV1VDOEhHY0o2ZERWRkpUZHMwYTRsbms1dHVheDQ0IiwiYnJhbmNoIjoiY3Vyc29yL2dpdGxhYi1pc3N1ZS13b3JrZmxvdy1jYjBkIiwicmVwb093bmVyIjoiUGxhc3RpY0RpZ2l0cyIsInJlcG9OYW1lIjoiY2w4eS1kZXgtdGVycmFjbGFzc2ljIiwicHJOdW1iZXIiOjczLCJjb21taXRTaGEiOiIwYTE4MTBlOTU5MTA1MmQ3NDU2NTE4N2FiZTVlYmNiNDE1ZTNlNTQyIiwicHJvdmlkZXIiOiJnaXRsYWIiLCJob3N0bmFtZSI6ImdpdGxhYi5jb20ifX0" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/fix-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/fix-in-web-light.png"><img alt="Fix in Web" width="99" height="28" src="https://cursor.com/assets/images/fix-in-web-dark.png"></picture></a></div> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0a1810e9591052d74565187abe5ebcb415e3e542. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
ghost1 commented 2026-06-05 10:12:55 +00:00 (Migrated from gitlab.com)

added 1 commit

  • f9b5b828 - fix: sync expiresAt when prefill limit edit from order book

Compare with previous version

added 1 commit <ul><li>f9b5b828 - fix: sync expiresAt when prefill limit edit from order book</li></ul> [Compare with previous version](/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/73/diffs?diff_id=1836419423&start_sha=0a1810e9591052d74565187abe5ebcb415e3e542)
ghost1 commented 2026-06-05 10:13:06 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
ghost1 commented 2026-06-05 10:13:10 +00:00 (Migrated from gitlab.com)

resolved all threads

resolved all threads
PlasticDigits commented 2026-06-05 10:14:12 +00:00 (Migrated from gitlab.com)

Security review

Commit reviewed: f9b5b8287f9d52c9ab91edd0d62b92baddbc22f2
Scope: frontend-dapp/src/pages/LimitOrdersPage.tsx, frontend-dapp/src/pages/LimitOrdersPage.test.tsx — mirrors existing /trade limit-order edit flow (buildLimitBookEditContext, isPriceOnlyLimitEdit, useLimitOrderUpdatePriceMutation) onto /limits. Includes follow-up commit syncing expiresAt on book Edit prefill.

Outcome: FINDINGS: 0 medium+

Analysis

Area Result
Authz / IDOR orderId for UpdateLimitOrderPrice is set only via book Edit on rows where order.owner === walletAddress (OrderBookPanel). On-chain execute_update_limit_order_price enforces o.owner == info.sender → Unauthorized otherwise. Client-side editContext tampering cannot amend another user's order.
Duplicate / unintended placement While editContext is set, placeLimitCombinedOk requires !editContext; non–price-only field changes set editNonPriceChanged and disable submit. Aligns with TradeOrderTicket.tsx.
Injection / XSS orderId rendered as a React number node; price validated by existing place gates and contract Decimal checks. No new HTML sinks or dynamic code paths.
Pause / governance (L6) UI disables Edit when paused; on-chain UpdateLimitOrderPrice also calls assert_not_paused. New test asserts Edit disabled when paused.
Secrets / SSRF / deps No changes in these areas.

Prior threads

  • Prior security review on 0a1810e (FINDINGS: 0) — still applies; no new attack surface in f9b5b82 (adds setExpiresAt(draft.expiresAt ?? null) so edit gating compares against the resting order).
  • Bugbot inline thread on missing expiresAt sync was a functional/UX defect (blocked price update or false non-price guard), not an authz or fund-loss path; resolved in f9b5b82. Not re-reported as a security finding.

Inline threads: none (no medium+ findings).

## Security review **Commit reviewed:** `f9b5b8287f9d52c9ab91edd0d62b92baddbc22f2` **Scope:** `frontend-dapp/src/pages/LimitOrdersPage.tsx`, `frontend-dapp/src/pages/LimitOrdersPage.test.tsx` — mirrors existing `/trade` limit-order edit flow (`buildLimitBookEditContext`, `isPriceOnlyLimitEdit`, `useLimitOrderUpdatePriceMutation`) onto `/limits`. Includes follow-up commit syncing `expiresAt` on book Edit prefill. **Outcome:** `FINDINGS: 0` medium+ ### Analysis | Area | Result | |------|--------| | **Authz / IDOR** | `orderId` for `UpdateLimitOrderPrice` is set only via book **Edit** on rows where `order.owner === walletAddress` (`OrderBookPanel`). On-chain `execute_update_limit_order_price` enforces `o.owner == info.sender` → `Unauthorized` otherwise. Client-side `editContext` tampering cannot amend another user's order. | | **Duplicate / unintended placement** | While `editContext` is set, `placeLimitCombinedOk` requires `!editContext`; non–price-only field changes set `editNonPriceChanged` and disable submit. Aligns with `TradeOrderTicket.tsx`. | | **Injection / XSS** | `orderId` rendered as a React number node; `price` validated by existing place gates and contract `Decimal` checks. No new HTML sinks or dynamic code paths. | | **Pause / governance (L6)** | UI disables Edit when paused; on-chain `UpdateLimitOrderPrice` also calls `assert_not_paused`. New test asserts Edit disabled when paused. | | **Secrets / SSRF / deps** | No changes in these areas. | ### Prior threads - Prior security review on `0a1810e` (FINDINGS: 0) — still applies; no new attack surface in `f9b5b82` (adds `setExpiresAt(draft.expiresAt ?? null)` so edit gating compares against the resting order). - Bugbot inline thread on missing `expiresAt` sync was a **functional/UX** defect (blocked price update or false non-price guard), not an authz or fund-loss path; resolved in `f9b5b82`. Not re-reported as a security finding. **Inline threads:** none (no medium+ findings).
PlasticDigits (Migrated from gitlab.com) merged commit 13422e60c4 into main 2026-06-05 10:38:24 +00:00
PlasticDigits commented 2026-06-05 10:38:25 +00:00 (Migrated from gitlab.com)

mentioned in commit 13422e60c4

mentioned in commit 13422e60c4d6a63063f0c1c92b9fc6c947d0c01d
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!791
No description provided.