Skip to content

Fix USDT approval in cleanupErc20sViaCall using safeApproveWithRetry#58

Open
clemsos wants to merge 2 commits into
mainfrom
claude/dreamy-einstein-kcP2C
Open

Fix USDT approval in cleanupErc20sViaCall using safeApproveWithRetry#58
clemsos wants to merge 2 commits into
mainfrom
claude/dreamy-einstein-kcP2C

Conversation

@clemsos

@clemsos clemsos commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replace the typed IERC20.approve() call in cleanupErc20sViaCall with solady's safeApproveWithRetry to support non-standard ERC20 tokens like USDT.

Key Changes

  • Updated RelayRouterV3.sol and RelayRouterV3_NonTstore.sol to use token.safeApproveWithRetry(to, amount) instead of IERC20(token).approve(to, amount) in the cleanupErc20sViaCall function
  • Added comprehensive regression test suite (RelayRouterV3UsdtCleanupTest.sol) covering two USDT quirks:
    1. USDT's approve returns no bool, causing typed calls to revert when decoding empty returndata
    2. USDT reverts when overwriting a non-zero allowance with a new non-zero value, requiring reset-to-zero before re-approval

Implementation Details

The fix addresses real-world compatibility issues with mainnet USDT (TetherToken):

  • safeApproveWithRetry handles tokens that don't return a bool from approve
  • It automatically resets allowance to zero before setting a new non-zero value, avoiding USDT's non-zero-to-non-zero revert
  • Tests verify both the full-balance consumption path and the residual allowance path where a target consumes less than the approved amount

This resolves the EXECUTION_REVERTED error that previously blocked gasless aUSDT → mUSD transactions.

https://claude.ai/code/session_01Rh3tu7gX2ztgQMvSRbG5pX

claude added 2 commits June 1, 2026 13:35
cleanupErc20sViaCall approved the call target with a typed
IERC20(token).approve(to, amount). Mainnet USDT does not return a bool
from approve(), so Solidity reverts when decoding the empty returndata,
surfacing as EXECUTION_REVERTED and blocking gasless aUSDT -> mUSD
conversions.

Replace the typed approve with solady's safeApproveWithRetry, which
tolerates tokens that do not return a bool on approve and resets the
allowance to zero before re-approving a non-zero value (the second USDT
quirk). All affected routers already declare 'using SafeTransferLib for
address', so no new imports are required.

Applies to the active v3 routers only; the deprecated v2.1 routers are
left untouched.

Refs: DEC-1106
Add mainnet-fork regression tests for the safeApproveWithRetry fix in
cleanupErc20sViaCall, exercising both real USDT quirks against the live
token and covering both changed v3 routers (tstore + non-tstore):

- approve no longer reverts on USDT's missing bool return (the original
  EXECUTION_REVERTED that blocked gasless aUSDT -> mUSD)
- the residual non-zero allowance path: when a target consumes less than
  the approved amount, the next cleanup re-approves over the residual
  without reverting (USDT's non-zero-over-non-zero approve guard)

Also documents the root cause: a typed approve against real USDT reverts
while decoding a bool from empty returndata.

Refs: DEC-1106
@clemsos clemsos requested review from fortoon21 and georgeroman June 2, 2026 16:19
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.

2 participants