Yield tokens are distributed based on proportion. Due to rounding operations in the calculation, there may be some remaining tokens at the end; those remaining tokens are sent to the last holder. In the contract, those remaining tokens are sent to the holder at lastProcessedIndex. If this holder is in the blacklist, the remaining tokens will not be sent and will get stuck in the contract.
Vulnerability Details
In ArcToken.sol#distributeYield(), lastProcessedIndex is the index of the last holder, not the last non-blacklist holder. If the last holder is in the blacklist, remaining yield tokens will be stuck in the contract.
Example scenario (7 holders: Owner, Alice, Bob, Charlie, Lily, Lucy, Tom — Tom is blacklisted). Each holds 1e18 tokens. Distributing 1000 yield tokens results in 166 tokens per eligible holder × 6 = 996 distributed; 4 yield tokens remain. The contract attempts to send the leftover 4 tokens to the holder at lastProcessedIndex — but if that holder (Tom) is blacklisted, those 4 tokens remain in the contract.
Recommended fix (ensure lastProcessedIndex points to the last non-blacklisted holder and use effective supply computed over allowed holders):
Suggested patch: distributeYield(...) in ArcToken.sol
Remaining yield tokens will be left in the contract. Over time, as more distributions occur, accumulated stuck balances will grow and make the available yield pool less than expected.
Add a new file distribute.t.sol to /test and run the test: forge test --via-ir --match-path test/distribute.t.sol --match-contract DistributeTest --match-test test_YieldDistribution_WithBlacklist_last -vv
Initial yield balance is 7000000000000000000, after distribute, total yield balance is 6999999999999999996.