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?