52925 sc medium usdt like approval hygiene can block subsequent operations after partial fill leaves non zero allowance
Submitted on: Aug 14th 2025 at 10:49:21 UTC by @LoopGhost007 for Attackathon | Plume Network
Report ID: #52925
Report Type: Smart Contract
Report severity: Medium
Target: https://github.com/immunefi-team/attackathon-plume-network-nucleus-boring-vault/blob/main/src/helper/DexAggregatorWrapperWithPredicateProxy.sol
Impacts:
Smart contract unable to operate due to lack of token funds
Temporary freezing of funds for at least 24 hours
Description
Brief / Intro
A medium-severity vulnerability was identified in DexAggregatorWrapperWithPredicateProxy related to allowance hygiene for non-standard ERC20 tokens (USDT-like). Some tokens refuse to change a spender allowance from a non-zero value directly to another non-zero value (they require approve(0) first). The wrapper uses safeApprove(spender, amount) without zeroing a prior non-zero allowance, so a partial fill that leaves a non-zero residual allowance can cause subsequent calls to revert on USDT-like tokens.
The PoC demonstrates that after a partial fill the wrapper left a large non-zero allowance to the router. On the next interaction, USDT-like tokens would revert when attempting a non-zero -> non-zero approve.
Vulnerability Details
Affected helper sites in DexAggregatorWrapperWithPredicateProxy.sol:
_oneInchHelper: callssafeApprove(aggregator, amount)without first zeroing allowance_okxHelper: callssafeApprove(okxApprover, amount)without first zeroing allowanceVault approvals: approvals to the vault do not follow a zero-first pattern
Behavior that causes failure:
A partial fill leaves a non-zero residual allowance to the router/approver.
USDT-like tokens revert when moving directly from a non-zero allowance to another non-zero allowance unless
approve(0)is called first.The wrapper’s
safeApprove()pattern does not zero-out allowances (nor necessarily clear leftover allowances after use), so operations can revert.
Evidence from PoC logs:
“Leftover USDT-like allowance to aggregator after partial fill: 999999999999”
“Second call should revert due to USDT-like non-zero -> non-zero approve rule.”
“VULNERABILITY CONFIRMED: Approval hygiene blocks subsequent operations.”
Note: In the provided run, the second call reverted earlier during transferFrom due to the test user balance being exhausted. The key precondition (leftover non-zero allowance after partial fill) is shown; with sufficient balance the wrapper would hit safeApprove over a non-zero current allowance and revert under USDT-like rules.
Impact Details
This can cause the wrapper to be unable to operate with USDT-like tokens after a partial fill leaves a non-zero allowance, temporarily freezing affected assets and impairing UX and reliability.
Suggested Mitigation
Always use a reset-to-zero then set pattern for approvals:
If
allowance(spender) != 0, callsafeApprove(spender, 0)beforesafeApprove(spender, amount).
Eliminate residual allowances after use:
After the swap completes,
safeApprove(spender, 0).This reduces attack surface if a spender becomes compromised.
Handle partial fills to avoid leftover allowances altogether:
Refund unspent
srcTokento the user.Zero-out the allowance to the router/approver after the call.
Apply these patterns in _oneInchHelper, _okxHelper, and any other approval locations (e.g., vault approvals).
Proof of Concept
Summary: The PoC uses a USDT-like token which rejects approve() calls that move a non-zero allowance to another non-zero value. After a partial fill the wrapper leaves a non-zero allowance to the aggregator. A second call with the same token will fail unless the wrapper first resets the allowance to zero.
Was this helpful?