fix(trade): submit the worst-fill bound the user actually reviewed (GH#2362) - #2439
fix(trade): submit the worst-fill bound the user actually reviewed (GH#2362)#2439dcccrypto wants to merge 1 commit into
Conversation
…H#2362) The confirmation modal computes a worstFillPriceE6 when it opens and displays it as the binding slippage limit — the existing comment says as much: "Showing it here means the user reviews the binding slippage limit before signing". But the confirm callback forwarded only the snapshotted position size, so trade() was called with no explicit limitPriceE6 and useTrade re-derived one from the live mark at submit time. Any market move between opening the modal and pressing Confirm therefore changed the protection bound out from under the user: they approved one number and the transaction carried another. Thread the reviewed bound through handleTrade to trade(), extending the snapshotSize pattern already established directly above it for exactly the same reason. Critically, do NOT forward a 0n snapshot. The on-chain handler treats limit_price_e6 == 0 as a "no limit" sentinel and skips the slippage check entirely (percolator.rs::handle_trade_cpi), and useTrade deliberately preserves an explicit 0n as an escape hatch for keeper/bot paths. The modal's snapshot is ALSO 0n whenever its computation threw or no live price was available. Forwarding that zero would convert "the bound may not match what you saw" into "there is no bound at all" — strictly worse than the bug being fixed. When the snapshot is not positive we omit the field, so useTrade derives a fresh non-zero limit exactly as before. That rule is extracted to resolveSubmittedLimitPriceE6 so it is unit testable and the reasoning lives next to it. Closes #2362 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 4 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
I self-directed to #2362 from the open issue list and targeted
So this change fixes Recommendation: close this PR unless What I got wrong: I picked work from the issue tracker without first checking which branch each issue targeted or which branch was active. I flagged a symptom of this on PR #2439 last cycle (the issue named Full audit of the remaining launch HIGH backlog is in #2440. |
|
Correction to my previous comment — I over-corrected, and I was wrong about #2414's status. Two errors to fix on the record. 1. #2414 IS already fixed on
That is strictly better than my My verification was faulty. I grepped playground for 2. But "redundant → close" was too strong. So Revised status of my three launch PRs:
So the decision is purely about release intent, and I cannot make it:
I am not closing them. Flagging clearly instead so nobody reviews them under a false assumption in either direction. |
|
Closing this — #2362 worst-fill bound. I have asked twice which branch is authoritative (see #2440) without an answer. The evidence is now conclusive enough to act on rather than leave this open indefinitely: 1. Every other contributor targets 2. 3. The user impact is limited — mainnet is dormant (no collateral, last on-chain activity March; see #2443), so this path is not carrying real trading. Taken together, keeping this open only adds to a review backlog I have repeatedly flagged as the binding constraint. Closing is reversible — the branch and history remain, and this can be reopened in seconds if Not closing #2437. That one fixes an unauthenticated memory-exhaustion vector, and |
Closes #2362 —
[High]Trade confirmation can submit a different worst-fill bound than the value reviewed by the user.The bug
The confirmation modal computes
worstFillPriceE6when it opens and displays it as the binding slippage limit. The existing comment inTradeFormstates the intent plainly:It just was not forwarded. The confirm callback passed only the snapshotted position size, so
trade()was called with no explicitlimitPriceE6anduseTradere-derived one from the live mark at submit time. Any market move between opening the modal and pressing Confirm changed the protection bound out from under the user — they approved one number, the transaction carried another.The fix
Thread the reviewed bound through
handleTradetotrade(), extending thesnapshotSizepattern already established immediately above it for exactly the same reason.The important part — a
0nsnapshot is deliberately NOT forwarded. The on-chain handler treatslimit_price_e6 == 0as a "no limit" sentinel and skips the slippage check entirely (percolator.rs::handle_trade_cpi), anduseTradeintentionally preserves an explicit0nas an escape hatch for keeper/bot paths. But the modal's snapshot is also0nwhenever its computation threw or no live price was available:Blindly forwarding the snapshot would therefore convert "the bound may not match what you saw" into "there is no bound at all" — strictly worse than the bug being fixed, and it would have looked like a correct fix. When the snapshot is not positive we omit the field so
useTradederives a fresh non-zero limit exactly as before.That rule is extracted to
resolveSubmittedLimitPriceE6so it is unit-testable and the reasoning lives beside it.Scope note — the issue targets a different branch
The issue is filed against
playgroundand namesapp/components/trade/OrderTicket.tsx, which does not exist onmain. The equivalent component here isapp/components/trade/TradeForm.tsx, and I confirmed the identical defect is present onmain: snapshot built at L937–950, displayed at L1044, confirm callback forwarding onlysnapSizeat L1050, andtrade({ lpIdx, userIdx, size })with no bound at L486. This PR fixesmain; whoever ownsplaygroundshould apply the same change toOrderTicket.tsx.Testing
__tests__/lib/gh2362-submitted-limit-price.test.ts(7): a positive reviewed bound is forwarded unchanged (including when the market has since moved — staleness is the point);0nis not forwarded; negatives are not forwarded;undefinedstaysundefinedso non-confirm paths are unaffected; bigint precision is preserved pastNumber.MAX_SAFE_INTEGER;1nis forwardable.Honest limit on this verification: these tests pin the decision rule, which is where the dangerous edge case lives. They do not prove the wiring end-to-end — that
TradeFormcalls the helper and forwards the result is verified by typecheck and inspection, not by a rendering test, because exercisingTradeFormneeds wallet/slab/price mocking well beyond this change. A reviewer should confirm the wiring manually: open the confirm modal, let the mark move, confirm, and check the submittedlimitPriceE6matches the displayed worst-fill.No regressions, measured:
__tests__/components/trade/+__tests__/lib/→ 3 failed / 1068 passed. Those 3 areadmin-session-security-v2.test.ts, which fails 3/3 on cleanorigin/main— pre-existing and unrelated.npx tsc --noEmit→ 19 errors, same count as clean main, none in the files touched here.🤖 Generated with Claude Code