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

51847 sc critical dos via dust leftover in erc 20 approvals

Submitted on Aug 6th 2025 at 07:52:55 UTC by @BeastBoy for Attackathon | Plume Network

  • Report ID: #51847

  • Report Type: Smart Contract

  • Report severity: Critical

  • Target: https://github.com/immunefi-team/attackathon-plume-network-nucleus-boring-vault/blob/main/src/helper/DexAggregatorWrapperWithPredicateProxy.sol

  • Impacts:

    • Protocol insolvency

    • Permanent freezing of funds

Description

The _oneInchHelper does:

depositAsset.safeApprove(address(aggregator), depositAmount);
(supportedAssetAmount,) = aggregator.swap(executor, desc, data);

Because 1inch supports partial fills, it may pull only 995 of 1,000 USDT and return 5 USDT to the wrapper. The allowance is decremented by only the 995 consumed, leaving 5 units of non-zero allowance behind, so the next safeApprove(..., newAmount) becomes a forbidden non-zero→non-zero change and reverts.

In the vault deposit path the code calculates shares via:

uint256 shares = depositAmount.mulDivDown(ONE_SHARE, rate);
vault.enter(msg.sender, asset, depositAmount, to, shares);

ERC-4626–style math rounds down, so depositing 1,000 units might mint only 999 shares, leaving 1 unit of asset stuck in the vault. That residual asset can combine with other dust vectors to block future approvals or withdrawals.

When interacting with a fee-on-transfer token the proxy calls:

That 1-unit dust makes the next approve(…,50) a non-zero→non-zero change and permanently DoSes that token’s flow.

This issue also exists in other functions like TellerWithMultiAssetSupportPredicateProxy:deposit & TellerWithMultiAssetSupportPredicateProxy:depositAndBridge.

Impact

Any of these tiny “dust” scenarios—partial fills, rounding, or fees—leaves a residual allowance or balance that blocks all future safeApprove calls, causing an irreversible denial of service for that asset.

Recommendation

Always reset the existing allowance to zero before setting a new one:

or perform a one-time infinite approval (type(uint256).max) to eliminate all non-zero→non-zero transitions.

Either zero-then-set or use infinite approval are acceptable mitigations. Choose infinite approval only if trust and security considerations for that spender are acceptable.

Proof of Concept

1

Setup and initial approval

2

Simulate partial consumption

3

Attempt to set a new non-zero allowance (reverts)

4

Demonstrate the fix: reset to zero then approve

Was this helpful?