52439 sc high dust accumulation in batched yield payouts leaves tokens stranded
Submitted on Aug 10th 2025 at 17:50:42 UTC by @Afriauditor for Attackathon | Plume Network
Report ID: #52439
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcToken.sol
Impacts:
Permanent freezing of funds
Description
Brief / Intro
distributeYieldWithLimit pays yield in batches but never reconciles integer-division remainders (“dust”) across the entire round. Because the function pulls the full totalAmount on the first batch and truncates per-holder payouts in each batch, the leftover dust remains in the contract with no sweep or final settlement. Over time this strands yield tokens and underpays holders.
Vulnerability Details
In the batched path:
if (startIndex == 0) {
yToken.safeTransferFrom(msg.sender, address(this), totalAmount);
}
...
// computed once per call, for all holders
uint256 effectiveTotalSupply = ... // sum of eligible balances
// for each holder in this batch only
uint256 share = (totalAmount * holderBalance) / effectiveTotalSupply;
yToken.safeTransfer(holder, share);
amountDistributed += share;
...
if (nextIndex == 0) {
emit YieldDistributed(totalAmount, yieldTokenAddr);
}The full
totalAmountis deposited on the first batch.Payouts use integer division per holder and per batch; truncation leaves a remainder.
There is no logic at the end of the final batch (
nextIndex == 0) to transfer the round’s remainder to anyone or to sweep it.Unlike the single-shot
distributeYield(which pays the final remainder to the last holder), the batched version never assigns the cumulative leftover, so the deposited yield minus the sum of all per-holder share transfers (across all batches) sits in the contract.Because the contract has no sweep/withdraw for stuck yield and subsequent calls to
distributeYieldWithLimitdeposit newtotalAmount(rather than using existing balance), the dust accumulates permanently.
Impact Details
Permanent freezing of funds: The unallocated remainder of each batched distribution stays in the token contract with no recovery path.
Proof of Concept
References
Target source: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcToken.sol
Was this helpful?