52468 sc insight dos in batch yield distribution due to cross batch state inconsistency
Submitted on Aug 11th 2025 at 02:48:59 UTC by @flora for Attackathon | Plume Network
Report ID: #52468
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcToken.sol
Impacts:
Smart contract unable to operate due to lack of token funds
Description
Brief/Intro
The distributeYieldWithLimit function in ArcToken.sol is vulnerable to a Denial of Service (DoS) attack. An administrative action, such as blacklisting a user for yield, performed mid-distribution between batches can create a state inconsistency. This causes subsequent distribution transactions to revert due to a shortfall in required funds. If exploited, this vulnerability would permanently halt the yield distribution process, preventing users from receiving their entitled yield and locking the remaining undistributed funds within the contract, requiring privileged intervention to resolve.
Vulnerability Details
Fund transfer and batch model
At the start of the first batch (
startIndex == 0), the contract pulls thetotalAmountfrom the distributor (msg.sender).Funds are transferred only once at the beginning.
Code snippet:
if (startIndex == 0) {
yToken.safeTransferFrom(msg.sender, address(this), totalAmount);
}State recalculation per batch
For every batch, the contract iterates through all holders to recalculate
effectiveTotalSupply, only including those currently eligible for yield.The denominator is recalculated in every batch, allowing state changes to interfere.
Code snippet:
uint256 effectiveTotalSupply = 0;
for (uint256 i = 0; i < totalHolders; i++) {
address holder = $.holders.at(i);
if (_isYieldAllowed(holder)) {
effectiveTotalSupply += balanceOf(holder);
}
}Flawed calculation across batches
The share for each user is calculated using
(totalAmount * holderBalance) / effectiveTotalSupply.The numerator (
totalAmount) remains fixed across all batches, while the denominator (effectiveTotalSupply) can change between batches.If a large holder becomes ineligible after their batch, the denominator decreases for subsequent batches while the numerator remains the same, inflating subsequent per-holder shares.
The inflated shares can exceed the remaining balance in the contract and cause a revert (
ERC20InsufficientBalance), permanently halting the distribution.
Code snippet:
uint256 share = (totalAmount * holderBalance) / effectiveTotalSupply;Impact Details
This vulnerability has a high-severity impact, as it breaks a core protocol function and freezes funds.
Temporary Freezing of Funds: undistributed yield tokens can become locked inside the
ArcTokencontract. In the PoC, 360 USDC (out of 1200 USDC) were trapped and irrecoverable without privileged administrative action.Denial of Service on Core Functionality: once the DoS state is triggered, no further batches can be processed, preventing users from receiving scheduled yield.
Loss of Yield for Users: users scheduled in subsequent batches cannot receive their yield.
References
Vulnerable Contract:
src/ArcToken.solVulnerable Function:
distributeYieldWithLimit(Lines 466-555)
Proof of Concept
Notes / Recommendations (implied by issue)
The root cause is using a fixed
totalAmountacross batches while recalculatingeffectiveTotalSupplyper-batch. Fixes should ensure per-batch calculations use the remaining undistributed amount or a per-batch numerator that aligns with the denominator to prevent inflation when eligibility changes mid-process.Consider one of:
Pulling funds per-batch proportional to the remaining undistributed amount.
Computing shares using a snapshot of eligible supply at the start and retaining that snapshot across batches.
Locking eligibility state for the duration of distribution or providing a single-batch distribution only.
Any fix must preserve security against reentrancy and race conditions while ensuring the sum of per-batch transfers cannot exceed the contract balance.
Was this helpful?