#42214 [SC-High] Leftover `WBERA` and `YEET` sent to `StakeV2` instead of to user who is claiming rewards
Submitted on Mar 21st 2025 at 18:24:40 UTC by @Oxl33 for Audit Comp | Yeet
Report ID: #42214
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/contracts/Zapper.sol
Impacts:
Theft of unclaimed royalties
Description
Description:
When users claim rewards through one of the claim functions in StakeV2, after removing liquidity from Kodiak and swapping, commonly (if not always) there will be left unused WBERA and YEET tokens. The issue lies in the use of _msgSender() as the receiver of the leftover tokens, because when StakeV2 calls Zapper, msg.sender is not the user who initiated the transaction, but StakeV2 contract. Due to this, one of the leftover tokens (or in the case of claimRewardsInToken function - both WBERA and YEET) get sent to StakeV2 and later added to rewards that other stakers can claim, when in reality, these leftover tokens belong to the user who initiated the transaction, because they originated from Kodiak LP tokens that the user had the right to claim as reward, based on their staked YEET amount and the duration of their staking.
WBERA and/or YEET tokens can be left unused during the claiming process in these cases:
If
Kodiakreturns bigger than expected output amounts ofWBERAandYEETafter burningKodiakLP tokensDuring swaps from
WBERAtoYEETand vice versa, if expected input amount is smaller than actual output fromKodiakDuring swaps from
WBERA/YEETto another whitelisted token (e.g.KDKoroBERO), if expected input amount is smaller than actual output fromKodiak
Considering all these possible cases and the fact that claiming rewards is one of the main functionalities of this protocol, I believe this issue will happen commonly and will affect the majority of the users.
The issue occurs in these functions:
StakeV2::claimRewardsInNative -> Zapper::zapOutNative -> _swapToWBERA - only YEET sent to StakeV2
StakeV2::claimRewardsInToken0 -> Zapper::zapOutToToken0 - only YEET OR only WBERA sent to StakeV2
StakeV2::claimRewardsInToken1 -> Zapper::zapOutToToken1 - only YEET OR only WBERA sent to StakeV2
StakeV2::claimRewardsInToken -> Zapper::zapOut - both YEET AND WBERA sent to StakeV2
Impact:
I think the most fitting impact from all the in-scope impacts is Theft of unclaimed royalties, because the affected users lose a part of the royalties that belongs to them and other stakers benefit from them, so it is similar to theft.
This impact is meant for high severity issues and I believe it fits the overall severity of this issue, because this issue will occur frequently (high likelihood) and the funds of users are mismanaged, which leads to undeserved losses for some users and gains for others (high impact).
Proof of Concept
Proof of Concept:
Alice stakes her YEET tokens using
StakeV2A percentage of BERA from yeets of users gets sent to
StakeV2Manager distributes the rewards with
executeRewardDistributionandexecuteRewardDistributionYeetfunctionsAlice wants to claim her share of the rewards and she wants to receive them in the form of e.g.
oBEROtokens, so she callsStakeV2::claimRewardsInTokenand specifies output token asoBERO(assuming it is whitelisted, because it is used incompound)Zapper::zapOutfunction gets called and there are leftover tokens ofWBERAandYEET, due to the reasons I mentioned inDescriptionsection_clearUserDebtgets called withtoken0Debtandtoken1Debtbeing more than 0, and_msgSender()beingStakeV2contractLeftover tokens that rightfully belong to Alice get sent to
StakeV2contract
Zapper::zapOut function gets called here: https://github.com/immunefi-team/audit-comp-yeet/blob/da15231cdefd8f385fcdb85c27258b5f0d0cc270/src/StakeV2.sol#L391
_clearUserDebt function gets called here (_msgSender() is StakeV2): https://github.com/immunefi-team/audit-comp-yeet/blob/da15231cdefd8f385fcdb85c27258b5f0d0cc270/src/contracts/Zapper.sol#L352
Was this helpful?