The RevenueHandler contract has a checkpoint() function that records revenue amounts for each token in each epoch. There is a mistake in the logic of non-alchemic tokens, because it assumes the entire token balance is new revenue for the epoch. This is wrong because the token balance includes pending revenue from previous epochs that have not yet been claimed. This allows users to claim more tokens than they should, which steals tokens from other users who try to claim later.
Vulnerability Details
The checkpoint() function in RevenueHandler contains the following code snippet:
uint256 thisBalance =IERC20(token).balanceOf(address(this));// If poolAdapter is set, the revenue token is an alchemic-tokenif (tokenConfig.poolAdapter !=address(0)) {// Treasury only receives revenue if the token is an alchemic-token treasuryAmt = (thisBalance * treasuryPct) / BPS;IERC20(token).safeTransfer(treasury, treasuryAmt);// Only melt if there is an alchemic-token to melt to amountReceived =_melt(token);// Update amount of alchemic-token revenue received for this epoch epochRevenues[currentEpoch][tokenConfig.debtToken] += amountReceived;} else {// If the revenue token doesn't have a poolAdapter, it is not an alchemic-token amountReceived = thisBalance;// Update amount of non-alchemic-token revenue received for this epoch epochRevenues[currentEpoch][token] += amountReceived;}
Notice that in the else case, the epochRevenues[currentEpoch][token] amount is incremented by the current token balance. As mentioned above, this is incorrect because the token balance also contains unclaimed revenue from previous epochs. Due to this mistake, the revenues past the first epoch will be inflated, which leads to an inflated totalClaimable value in the _claimable() function:
Once an epoch has an inflated revenue, a malicious user can call claim() to take a large amount of tokens they do not deserve. This is a theft of other users' unclaimed yield. Since the malicious user would receive the tokens, no tokens would be left in the RevenueHandler for the other users to claim. See the PoC for an example.
References
See the PoC below.
Proof of Concept
I have created the following test case that can be added to RevenueHandler.t.sol: