51802 sc low temporary freeze of rewards is possible if efficientsupply 0
Submitted on Aug 5th 2025 at 21:33:32 UTC by @Santi for Attackathon | Plume Network
Report ID: #51802
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcToken.sol
Impacts: Permanent freezing rewards
Description
Brief/Intro
Rewards tokens may be stuck on the contract. This can happen if the effectiveSupply for distribution is equal to 0.
Vulnerability Details
For yield token distribution, the user with role YIELD_DISTRIBUTOR_ROLE must call ArcToken.distributeYield() or ArcToken.distributeYieldWithLimit().
The function ArcToken.distributeYield() transfers rewards token before the effectiveTotalSupply == 0 check, which returns from the function if it is true.
Code snippet:
function distributeYield(
uint256 amount
) external onlyRole(YIELD_DISTRIBUTOR_ROLE) nonReentrant {
ArcTokenStorage storage $ = _getArcTokenStorage();
....
ERC20Upgradeable yToken = ERC20Upgradeable(yieldTokenAddr);
yToken.safeTransferFrom(msg.sender, address(this), amount); // <- transfer yield tokens to contract
uint256 distributedSum = 0;
uint256 holderCount = $.holders.length();
if (holderCount == 0) {
emit YieldDistributed(0, yieldTokenAddr);
return;
}
uint256 effectiveTotalSupply = 0;
for (uint256 i = 0; i < holderCount; i++) {
address holder = $.holders.at(i);
if (_isYieldAllowed(holder)) {
effectiveTotalSupply += balanceOf(holder); // <- calculate effectiveTotalSupply
}
}
if (effectiveTotalSupply == 0) {
emit YieldDistributed(0, yieldTokenAddr);
return; // <- return from function if effectiveSupply == 0, but contract stores yield tokens.
}
...
}It is incorrect to transfer yield tokens before the effectiveTotalSupply check. If effectiveSupply is equal to zero (for example, if all holders are blacklisted for yield distribution), the yield tokens will be stuck on the contract.
For comparison, ArcToken.distributeYieldWithLimit() checks that effectiveSupply != 0 before tokens transfer. Link:
https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcToken.sol#L518-L528
Impact Details
It is possible to lose all yield tokens that the YIELD_DISTRIBUTOR_ROLE attempted to distribute. This is a rare case because:
All token holders must be ineligible for yield tokens (e.g., blacklisted).
YIELD_DISTRIBUTOR_ROLEmust call this function when all holders are ineligible.
Recovery is possible via ArcToken.distributeYieldWithLimit(), but with constraints:
A new token holder must appear, and the new token holder must be at index > 0 (for example, it can be admin).
The new token holder must not be blacklisted.
Then YIELD_DISTRIBUTOR_ROLE can call ArcToken.distributeYieldWithLimit() and pass the correct totalAmount, startIndex, maxHolders.
Recovery is possible, but yield tokens will be temporarily frozen and distribution will be incorrect. That is why this finding is rated Low and ArcToken.distributeYield() should handle this case correctly.
References
distributeYield() function: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcToken.sol#L388
Proof of Concept
Was this helpful?