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.
Residual non-zero allowances caused by partial token consumption, rounding, or fees can permanently prevent subsequent non-zero allowance updates for tokens that forbid non-zero → non-zero transitions (e.g., USDT). This results in a DoS that can freeze deposits/withdrawals or cause protocol insolvency.
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.
Proof of Concept
Was this helpful?