Skip to content

feat: permissionless cancelExpiredSwap for stuck trading orders#180

Merged
jtfirek merged 1 commit into
feat/eth-asset-supportfrom
feat/keeper-cancel-expired-swap
Jul 10, 2026
Merged

feat: permissionless cancelExpiredSwap for stuck trading orders#180
jtfirek merged 1 commit into
feat/eth-asset-supportfrom
feat/keeper-cancel-expired-swap

Conversation

@jtfirek

@jtfirek jtfirek commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #124 (feat/eth-asset-support). Lets the backend keeper clear an expired trading order without a user signature, releasing the stuck CashModule solvency hold and freeing the one-active-order-per-safe slot.

  • Adds cancelExpiredSwap(address safe) to both EnsoSwapModule and AcrossSwapModule (covers all trading order types: buy / sell / swap).
  • Permissionless (same trust model as executeSwap) and gated on block.timestamp > order.deadline. Once past the deadline executeSwap can only revert OrderExpired, so the order is already dead — this just cleans it up.
  • Mirrors cancelSwap's clearing path: cashModule.cancelWithdrawalByModule(safe) on OP, or a direct delete + SwapCancelled where cashModule == 0 (mainnet trading).
  • New OrderNotExpired error for calls at/before the deadline; NoActiveOrder when nothing is stored.

Why it's safe

  • Can only run after expiry, i.e. when execution is already impossible.
  • It only refunds the safe's own funds back to the safe and clears state — there is no fund-movement authority a caller could abuse, and no destination/recipient input.
  • A stored swap only exists while the order is still requested (executeSwap deletes it before dispatching), so this can never race a partially-executed/bridged order.

Motivation

Today a quote can expire (deadline passes) before the keeper executes — e.g. route slippage moves — leaving the order permanently un-executable. The only recovery was the owner signing cancelSwap, which blocked the next trade and left the withdrawal hold in place. This gives the BE reaper a signature-less way to release it.

Once a stored swap passes its `order.deadline`, `executeSwap` can never
succeed again (it reverts `OrderExpired`), yet the swap and its CashModule
solvency hold sit locked until an owner signs `cancelSwap`. This blocks the
one-active-order-per-safe slot and leaves the withdrawal hold in place,
forcing the user to sign a cancel just to recover.

Add `cancelExpiredSwap(address safe)` to both EnsoSwapModule and
AcrossSwapModule (covers all trading order types: buy/sell/swap). It takes
no signature and can be called by anyone (e.g. the backend keeper) once
`block.timestamp > order.deadline`, mirroring `cancelSwap`'s clearing path
(`cancelWithdrawalByModule`, or direct delete when `cashModule == 0`).

Safe by construction: it can only run after expiry (when execution is
already impossible) and merely refunds the safe's own funds back to the
safe and clears state — there is no fund-movement authority to abuse.

Reverts `OrderNotExpired` at/before the deadline and `NoActiveOrder` when
nothing is stored. Adds forge coverage for both modules.

Co-authored-by: Cursor <cursoragent@cursor.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@jtfirek
jtfirek merged commit edba2b9 into feat/eth-asset-support Jul 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant