For the complete documentation index, see llms.txt. This page is also available as Markdown.

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: calls safeApprove(aggregator, amount) without first zeroing allowance

  • _okxHelper: calls safeApprove(okxApprover, amount) without first zeroing allowance

  • Vault 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, call safeApprove(spender, 0) before safeApprove(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 srcToken to 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.

PoC code (place under test/ and run with provided remappings)
Execution Logs

Run: forge test -vv --match-path test/USDT_Like_Approval_DoS.t.sol

Output:

Was this helpful?