49787 sc high batched yield distribution doesn t account for transfers purchases between batches
Submitted on Jul 19th 2025 at 13:31:56 UTC by @Vanshika for Attackathon | Plume Network
Report ID: #49787
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcToken.sol
Impacts:
Theft of unclaimed yield
Description
Brief / Intro
ArcToken has a function to distribute yield in batches when the number of token holders is too high to fit in one transaction. Any change in user balances sandwiched between separate batches can lead to transaction revert and loss of yield for some token holders.
Vulnerability Details
ArcToken::distributeYieldWithLimit() distributes yield to a specified range of token holders at a time. holders is an enumerable set of addresses among whom the total yield amount has to be distributed. When this is done in batches, if users at the beginning or middle can inflate their balance and get more yield between batches, the contract can run out of funds before it reaches the end.
The contract receives the totalAmount to distribute only at the first batch when startIndex == 0. Yield calculation for a token holder is:
uint256 share = (totalAmount * holderBalance) / effectiveTotalSupply;effectiveTotalSupply is calculated as the sum of ArcToken balances for all eligible yield recipients. This calculation occurs separately for each batch of transfers, so effectiveTotalSupply can change between batches. This leads to incorrect yield shares or function reverts. A user can purchase more ArcTokens right before early batches, receive an inflated share, and then sell the tokens to a new holder to DoS one or more later batches.
Impact Details
There is a high likelihood of accidental accounting errors and reverts during regular use of the function. This can also be exploited by malicious actors to steal yield from other token holders.
Proof of Concept
Expected Results
If maxHolders in batch 2 = 1, expected test output:
Ran 1 test for test/ArcToken.t.sol:ArcTokenTest
[PASS] test_batchDistribution() (gas: 599048)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.41ms (410.18µs CPU time)
Ran 1 test suite in 4.30ms (1.41ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)If you test with maxHolders = 2 or 3 for batch2 (uncomment David's assert), expected failing output:
Ran 1 test suite in 41.62ms (6.49ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/ArcToken.t.sol:ArcTokenTest
[FAIL: ERC20InsufficientBalance(0xc7183455a4C133Ae270771860664b6B7ec320bB1, 100000000000000000000 [1e20], 125000000000000000000 [1.25e20])] test_batchDistribution() (gas: 626814)
Encountered a total of 1 failing tests, 0 tests succeededWas this helpful?