52787 sc high batched yield distribution rounding in arctoken permanently freezes unclaimed funds and misreports payouts
Submitted on Aug 13th 2025 at 07:21:42 UTC by @manvi for Attackathon | Plume Network
Report ID: #52787
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
While testing ArcToken.distributeYieldWithLimit, I found that every batched yield distribution leaves behind a small amount of the yield token (“dust”) stuck in the contract due to integer division. This dust is never distributed to any holder and cannot be withdrawn, permanently freezing it. The function’s event emission also misreports payouts by claiming the full amount was distributed, even though part of it remains stuck. If this continues in production with frequent distributions, it will lead to a growing amount of inaccessible funds and misleading accounting data.
Vulnerability Details
In the smart contract, distributeYieldWithLimit processes yield payments in batches. For each holder in the batch, the share is calculated as:
share = (totalAmount * holderBalance) / effectiveTotalSupply;
Because integer division floors the result, any remainder for each holder is left undistributed in that batch. Unlike distributeYield, which tops off the final holder, this batched version never reconciles those remainders across batches.
Flow observed:
Code excerpt demonstrating the issue
uint256 share = (totalAmount * balanceOf(holder)) / effectiveTotalSupply;
yToken.safeTransfer(holder, share);Impact Details
Permanent freezing of funds - The frozen dust accumulates with every batch, creating a growing locked balance that no one can reclaim without a contract upgrade.
Misreported payouts - The event logs show the intended amount as distributed, creating a false record that can mislead operators, auditors, and external integrations.
Even a remainder of a few tokens per batch can add up over hundreds of distributions per year. At this scale, this could mean thousands of USDC locked away annually.
References
Smart contract - ArcToken.sol
Function call - distributeYieldWithLimit
Proof of Concept
If you want, I can:
Suggest minimal code fixes to avoid dust (e.g., carry remainders forward or top off last recipient of the entire distribution), or
Draft a suggested patch/PR compatible with the project style that reconciles leftover remainders and corrects the event emission.
Was this helpful?