52847 sc high no function to recover the remained yield by distributeyieldwithlimit
Submitted on Aug 13th 2025 at 15:43:57 UTC by @ubl4nk for Attackathon | Plume Network
Report ID: #52847
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcToken.sol
References: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcToken.sol#L451
Description
Brief / Intro
This vulnerability causes yield tokens to become permanently locked in the contract due to integer division rounding errors, with no direct mechanism to recover them. The impact is significant when yield tokens have high value (e.g., $1000 per token) or when distributions occur frequently (e.g., daily), leading to accumulating losses that can reach substantial amounts.
Vulnerability Details
distributeYieldWithLimitcomputes each holder's share as: (totalAmount * holderBalance) / effectiveTotalSupply Because Solidity integer division floors results, small rounding losses occur per-holder.The non-batched
distributeYieldcompensates by assigning the remainder to the last holder (e.g.,lastShare = amount - distributedSum), preventing rounding loss.distributeYieldWithLimitlacks any such final adjustment.Consequences:
Locked funds: Cumulative rounding remainders cause
totalAmount - amountDistributedto remain inside the contract with no direct recovery function.No recovery mechanism: Admin or other actors cannot reclaim the locked yield, even by changing batch parameters (
totalAmount,startIndex,maxHolders).Misleading event: When
nextIndex == 0, the function emitsYieldDistributed(totalAmount, yieldTokenAddr), implying the full amount was distributed while some tokens may remain locked.Scalability: Frequent distributions or many holders amplify the problem as rounding errors accumulate.
Impact Details
A small amount becomes semi-locked in the contract and cannot be recovered directly, accumulating over time and potentially becoming significant.
Proof of Concept
Assumptions for PoC
Contract deployed with a yield token (ERC20-like).
Holders array: 3 holders (A, B, C) with balances of 10 tokens each.
Total supply: 30 tokens (
effectiveTotalSupply = 30).Yield amount:
totalAmount = 100(units of yield token).Batch size: single batch with
maxHolders = 3.No restricted holders for simplicity.
Execute Batch: distributeYieldWithLimit(100, 0, 3)
Batch holders: A, B, C (all allowed).
Calculations per holder (Solidity integer division floors):
For A:
share = (100 * 10) / 30 = 1000 / 30 ≈ 33.33 -> 33
Transfer 33 to A
For B:
share = (100 * 10) / 30 = 33
Transfer 33 to B
For C:
share = (100 * 10) / 30 = 33
Transfer 33 to C
Distributed sum:
amountDistributed = 33 + 33 + 33 = 99
Contract state after distribution:
nextIndex = 0 (endIndex = 3 = totalHolders)
Event emitted: YieldDistributed(100, yieldTokenAddr) — misleading because only 99 distributed
Remaining locked in contract: 100 - 99 = 1
Key Evidence / Link
Relevant code: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcToken.sol#L451
Was this helpful?