#42732 [SC-High] Incomplete token return whena user claim his rewards leads to rewards fund loss
Was this helpful?
Was this helpful?
Submitted on Mar 25th 2025 at 13:54:49 UTC by @Le_Rems for
Report ID: #42732
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/StakeV2.sol
Impacts:
Theft of unclaimed yield
Theft of unclaimed royalties
If users claim rewards using claimRewardsInToken0
or claimRewardsInToken1
, the Zapper contract may return unused tokens to the StakeV2
contract instead of to the user who initiated the claim.
The root cause is in the Zapper contract's token handling logic. When redeeming vault shares, the Zapper receives both token0 and token1. It then swaps a portion of one token for the other based on parameters provided by the user. However, any remaining tokens that aren't swapped are sent back to the calling contract (StakeV2
) rather than to the user who initiated the claim.
This behavior is highly likely, as market conditions will change between the time the user calculates the swap inputs off-chain and when the transaction is executed.
This results in users receiving only part of their earned rewards, with the remainder being trapped in the StakeV2
contract where they effectively become part of the pool's general rewards, benefiting all stakers rather than the specific user who earned them.
Update the zapOutToToken0
and zapOutToToken1
functions in the Zapper contract to send all tokens to the specified receiver rather than sending some back to the caller:
A user has earned rewards and calls StakeV2::claimRewardsInToken0
to claim them
StakeV2
calls Zapper::zapOutToToken0
which:
Redeems vault shares, receiving both token0 and token1
Swaps a portion of token1 for token0 based on the swapData
parameter
Sends token0 to the user (receiver)
Sends any remaining token1 back to StakeV2
(the _msgSender()
)
The relevant code in Zapper::zapOutToToken0
:
The issue is that token0Debt
(remaining token0) is sent to _msgSender()
(the StakeV2
contract) instead of to the user who initiated the claim.
These tokens become "stuck" in the StakeV2
contract and are counted as part of accumulatedDeptRewardsYeet()
, which distributes them to all stakers rather than returning them to the original user.