Skip to content

improve(tx-utils): preserve simulation-failure reason through willSucceed fallbacks#3466

Open
droplet-rl wants to merge 2 commits into
masterfrom
droplet/T90K0AL22-C0AB30NK4LX-1780657234-684319-willsucceed-reason
Open

improve(tx-utils): preserve simulation-failure reason through willSucceed fallbacks#3466
droplet-rl wants to merge 2 commits into
masterfrom
droplet/T90K0AL22-C0AB30NK4LX-1780657234-684319-willsucceed-reason

Conversation

@droplet-rl

Copy link
Copy Markdown
Contributor

Summary

Slack discussion: continuation of the gasless stuck-queue reporting thread (PRs #3464, #3465).

willSucceed had two places where useful error context disappeared:

  1. Lost callStatic error. When callStatic threw an error without a custom errorName, the catch block silently fell through to the estimateGas fallback and discarded the underlying error. If estimateGas then surfaced something less specific, the original cause was unrecoverable.
  2. "unknown error" placeholder. estimateGas errors that weren't typeguards.isEthersError (e.g. RPC connection issues, raw rejections) collapsed to the literal string "unknown error", even when the thrown value was a perfectly good Error with a populated message.

Both made transaction-submission telemetry harder to interpret and prevented the gasless relayer (PR #3464) from carrying a meaningful reason in its typed simulation_failed outcome.

Change

  • Preserve the callStatic error across the fallback in a new callStaticError local.
  • Extract reason via a small _extractSimulationReason helper that walks:
    • ethers error.reason (preserves existing behavior on Polygon / SDK paths)
    • Error.message from the estimateGas reject
    • Error.message from the captured callStatic error
    • "unknown error" (last-resort sentinel)

Behavior unchanged when callStatic reports a custom error name, when estimateGas throws an ethers error with a populated reason, or when no inputs carry a message.

Doc updates considered

No AGENTS.md / README.md shifts. willSucceed's contract — { succeed, reason } — is unchanged; just the reason content is richer.

Test plan

  • yarn typecheck — clean.
  • yarn lint — clean.
  • yarn hardhat test test/TransactionClient.ts — 15/15 passing, including 5 new cases under "willSucceed simulation-failure reason preservation":
    • preserves a non-ethers estimateGas error message instead of 'unknown error'
    • falls back to the callStatic error message when estimateGas yields no message
    • uses ethers reason when estimateGas throws an ethers error (existing behavior)
    • returns 'unknown error' only when no message is recoverable anywhere
    • returns succeed:false with errorName when callStatic throws a custom error (existing behavior)

🤖 Generated with Claude Code

…ceed fallbacks

`willSucceed` silently dropped non-custom-error callStatic failures and
collapsed any non-ethers estimateGas error to the literal string
`"unknown error"`. That makes upstream telemetry (and the gasless
relayer's planned per-failure reporting) much harder to read.

Capture the callStatic error and let the eventual reason walk through
ethers `reason` → ethers/Error `message` → callStatic `message` →
literal "unknown error" only as a last resort.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 979fdd0470

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/utils/TransactionUtils.ts Outdated
Comment on lines +254 to +255
if (estimateGasError instanceof Error && estimateGasError.message) {
return estimateGasError.message;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prefer callStatic reason before generic gas errors

When callStatic throws a non-custom revert as an Error and the estimateGas fallback throws an ethers UNPREDICTABLE_GAS_LIMIT/generic Error without a populated reason, this branch returns the generic estimateGas.message before ever checking the preserved callStaticError.message. That still drops the more actionable revert context this change is trying to preserve for simulation-failure telemetry; the fallback should only use the estimate-gas message ahead of callStaticError when it is known to be the more specific signal.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed in the follow-up commit. Reordered the fallback so an ethers reason on either error still wins, but when neither has one, the callStatic message is preferred over the estimateGas message. Rationale: callStatic is the diagnostic call whose purpose is to surface revert detail, while estimateGas typically yields generic provider noise (UNPREDICTABLE_GAS_LIMIT, "cannot estimate gas; transaction may fail", etc.).

New priority order:

  1. estimateGasError.reason (ethers)
  2. callStaticError.reason (ethers)
  3. callStaticError.message
  4. estimateGasError.message
  5. "unknown error"

Added two regression tests:

  • prefers callStatic message over a generic estimateGas message when neither carries an ethers reason
  • prefers an ethers reason on the callStatic error over a callStatic message

When neither error carries an ethers `reason`, prefer the callStatic
message over the estimateGas message — callStatic exists to surface
revert detail, while estimateGas tends to throw generic provider noise
(UNPREDICTABLE_GAS_LIMIT etc.). Also check the callStatic error for an
ethers `reason` field before falling back to message strings.

Adds two regression tests covering the reordered priority.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@droplet-rl

Copy link
Copy Markdown
Contributor Author

Thanks for the review, @codex — addressed the P2 in commit cfde50f.

Change: reordered _extractSimulationReason so the priority is now

  1. estimateGasError.reason (ethers)
  2. callStaticError.reason (ethers)
  3. callStaticError.message
  4. estimateGasError.message
  5. "unknown error"

Previously the generic estimateGas Error.message could shadow a more actionable callStatic revert message. The new ordering reflects that callStatic is the purpose-built diagnostic call, while estimateGas tends to surface generic provider noise (UNPREDICTABLE_GAS_LIMIT etc.) when no ethers reason is populated.

Added two regression tests covering the new ordering. Local check: yarn typecheck, yarn lint, and yarn hardhat test test/TransactionClient.ts (17/17) all clean.

@codex please re-review.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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