Skip to content

🥅 server: handle terminal withdraw reverts#777

Open
cruzdanilo wants to merge 1 commit intomainfrom
withdraw
Open

🥅 server: handle terminal withdraw reverts#777
cruzdanilo wants to merge 1 commit intomainfrom
withdraw

Conversation

@cruzdanilo
Copy link
Member

@cruzdanilo cruzdanilo commented Feb 14, 2026


Open with Devin

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced detection and handling of terminal withdrawal reverts to prevent incomplete transaction states.
    • Improved error processing to classify withdrawal failures and abort operations when receipts indicate failure.
    • Strengthened withdrawal flow to remove queued entries for terminal errors and avoid unwanted retries.
  • Tests

    • Added and expanded tests covering keeper receipts, reverted/failed withdrawals, fingerprinting, and various revert scenarios.
  • Chores

    • Added a changeset to bump the patch version and note terminal withdraw handling.

@changeset-bot
Copy link

changeset-bot bot commented Feb 14, 2026

🦋 Changeset detected

Latest commit: 28ef827

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gemini-code-assist
Copy link

Summary of Changes

Hello @cruzdanilo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refines the withdrawal scheduling mechanism by introducing robust error handling for unrecoverable transaction reverts. It ensures that withdrawals failing due to specific terminal conditions are promptly removed from the processing queue, preventing unnecessary retries and improving system efficiency and error reporting. The changes enhance the reliability of the withdrawal process by accurately identifying and responding to definitive transaction failures.

Highlights

  • Terminal Withdraw Revert Handling: Implemented logic to identify and handle 'terminal' withdraw reverts, such as InsufficientAccountLiquidity or PreExecHookReverted with NoProposal, which indicate unrecoverable errors. These withdrawals are now removed from the processing queue immediately without retries.
  • Transaction Receipt Status Check: Modified the scheduleWithdraw function to check the transaction receipt status returned by keeper.exaSend. If a transaction reverts, the withdrawal is marked as aborted and removed from the Redis queue.
  • New Helper Function: Introduced a new helper function isTerminalWithdrawReason to centralize the logic for determining if a given error reason constitutes a terminal withdraw revert.
  • Enhanced Test Coverage: Added comprehensive test cases to validate the new terminal revert handling, including scenarios where InsufficientAccountLiquidity or NoProposal errors occur, both directly from contract simulation and when returned as a reverted receipt from the keeper.
Changelog
  • .changeset/steady-otter-handle.md
    • Added a new changeset file for the patch.
  • server/hooks/block.ts
    • Updated scheduleWithdraw to check transaction receipts for success status and handle reverts.
    • Modified error handling in scheduleWithdraw's catch block to use isTerminalWithdrawReason.
    • Introduced isTerminalWithdrawReason helper function to identify specific terminal revert conditions.
  • server/test/hooks/block.test.ts
    • Added imports for ensClient, fingerprintRevert, keeper, and onesignal.
    • Updated an existing test to use proposalManagerAbi directly for NoProposal error encoding.
    • Added new test cases for handling InsufficientAccountLiquidity and NoProposal reverts.
    • Included tests for scenarios where the keeper returns a reverted transaction receipt.
    • Added tests for capturing generic withdraw errors and non-error throwables.
    • Introduced tests for PreExecHookReverted without NoProposal and terminal reverts thrown by the keeper.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Feb 14, 2026

Walkthrough

Detects terminal withdrawal revert reasons and aborts scheduled withdrawals: keeper receipt status is checked; terminal reasons (via new isTerminalWithdrawReason) cause removal from the Redis queue and abort; error-reason extraction is centralized. Tests updated to cover new flows.

Changes

Cohort / File(s) Summary
Changeset
.changeset/steady-otter-handle.md
Adds patch changeset for @exactly/server noting "handle terminal withdraw reverts".
Core Logic
server/hooks/block.ts
Adds isTerminalWithdrawReason helper; scheduleWithdraw now awaits keeper receipt, passes ignore option, derives centralized revert reason from errors, aborts and removes Redis queue entry on terminal reasons or failed receipts.
Tests
server/test/hooks/block.test.ts
Adds and reworks tests covering keeper receipt success/failure, terminal error cases (InsufficientAccountLiquidity, NoProposal, etc.), Redis queue removal assertions, fingerprinting and captureException behavior, and keeper-related notification flows.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Block as server/hooks/block.ts
    participant Keeper as keeper
    participant Redis as RedisQueue
    participant Handler as ErrorHandler

    Client->>Block: triggerWithdraw()
    Block->>Block: scheduleWithdraw()
    Block->>Keeper: exaSend(txData, { ignore: isTerminalWithdrawReason })
    Keeper-->>Block: receipt { status, details? }

    alt receipt.status == "success"
        Block->>Redis: leave message queued
    else receipt.status != "success" or throw
        Block->>Block: extract revertReason from error/receipt
        Block->>Block: isTerminalWithdrawReason(revertReason)?
        alt terminal
            Block->>Redis: remove message from queue
            Block->>Handler: abort with terminal error
        else non-terminal
            Block->>Redis: keep message queued (retry later)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • nfmelendez
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: handling terminal withdrawal reverts in the server module, which aligns with the core modifications in block.ts and corresponding tests.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch withdraw

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces logic to handle terminal withdrawal reverts by identifying specific error reasons and preventing retries for them. The changes are well-tested. I've found a critical bug in the handling of ignored terminal errors that could lead to sending incorrect notifications. See my detailed comments below.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

@sentry
Copy link

sentry bot commented Feb 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.78%. Comparing base (5ef32de) to head (28ef827).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #777      +/-   ##
==========================================
+ Coverage   68.59%   68.78%   +0.19%     
==========================================
  Files         207      207              
  Lines        7005     7042      +37     
  Branches     2189     2207      +18     
==========================================
+ Hits         4805     4844      +39     
+ Misses       2010     2006       -4     
- Partials      190      192       +2     
Flag Coverage Δ
e2e 68.65% <81.81%> (+15.67%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Comment on lines +531 to +534
const isTerminalWithdrawReason = (reason: string) =>
reason === "InsufficientAccountLiquidity()" ||
(reason.startsWith("PreExecHookReverted(") &&
reason.endsWith(`,${encodeErrorResult({ errorName: "NoProposal", abi: proposalManagerAbi })})`));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider precomputing the encoded NoProposal bytes.

encodeErrorResult({ errorName: "NoProposal", abi: proposalManagerAbi }) is deterministic yet recomputed on every call to isTerminalWithdrawReason (called both by the ignore callback and in the catch block).

♻️ Hoist the encoded value
+const encodedNoProposal = encodeErrorResult({ errorName: "NoProposal", abi: proposalManagerAbi });
 const isTerminalWithdrawReason = (reason: string) =>
   reason === "InsufficientAccountLiquidity()" ||
   (reason.startsWith("PreExecHookReverted(") &&
-    reason.endsWith(`,${encodeErrorResult({ errorName: "NoProposal", abi: proposalManagerAbi })})`));
+    reason.endsWith(`,${encodedNoProposal})`));

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