#42292 [SC-High] zapper wrong convertion
Was this helpful?
Was this helpful?
Submitted on Mar 22nd 2025 at 14:12:16 UTC by @Ace30 for
Report ID: #42292
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/contracts/Zapper.sol
Impacts:
Permanent freezing of unclaimed yield
In the Zapper contract, the function zapOutToToken0
calls _yeetOut
to convert vault share tokens into token0
and token1
.
However, after receiving tokens, it does not swap all amount of the received token1
(token1Debt
) into token0
. Instead, it only swaps swapData.inputAmount
(a user-specified amount) of token1
. Additionally, the remaining token1Debt
is sent to msg.sender
(which is StakeV2
) and user does not get this part of his funds.
The same issue occurs in zapOutToToken1
, zapOutNative
, and zapOut
, leading to a partial swap of assets and loss of funds for users.
Users can claim staking rewards in token0
by calling StakeV2.claimRewardsInToken0
as shown below:
At step 2 (@2>
) three possible scenarios arise:
If swap.inputAmount > token1Debt
then the function will revert because of underflow
If swap.inputAmount < token1Debt
then the remaining token1Debt
is sent to StakeV2
, resulting in a loss for the user.
If swap.inputAmount = token1Debt
No issue occurs, but this scenario is very rare since amount of token 1 received (token1Debt
) fluctuates due to changes in vault share price and Kodiak liquidity.
Note: Since any action on Kodiak Island affects the received amount of token0 and token1, in the current codebase, users must provide an additional 1-2% inputAmount to account for these fluctuations and prevent reverts.
Note Similar issue happens in other StakeV2
claim functions too: claimRewardsInToken1
, claimRewardsInToken
, and claimRewardsInNative
.
Solution A better solution is to fully convert the received token1 into token0. To determine the minAmountOut for the swap (to mitigate slippage), the protocol can obtain the user’s acceptable slippage percentage, retrieve the on-chain price from the swapper, and calculate the minAmountOut accordingly.
All users trying to claim their staking rewards will experience either transaction revert or a partial loss of rewards
Zapper.zapOutToToken0(): https://github.com/immunefi-team/audit-comp-yeet/blob/da15231cdefd8f385fcdb85c27258b5f0d0cc270/src/contracts/Zapper.sol#L249
Step 1: User Initiates Staking Reward Claim
The user calls StakeV2.claimRewardsInToken0()
to redeem 100 vault share tokens and receive token0 as a reward.
Based on on-chain data, the user expects to receive:
200 token0
100 token1
The user sets swapData.inputAmount = 100
, expecting that all received token1
will be converted to token0
.
Step 2: zapOutToToken0()
Execution Begins
zapOutToToken0()
is called within StakeV2.claimRewardsInToken0()
.
Internally, it calls _yeetOut()
to:
Withdraw from the farm
Redeem vault shares from the Kodiak vault
Remove liquidity from Kodiak Island
Due to on-chain fluctuations (other transactions affecting liquidity), the user actually receives:
210 token0
110 token1
Step 3: Partial Token1 Swap Occurs
The contract executes the following steps inside zapOutToToken0()
:
Swaps swapData.inputAmount = 100
of token1
into token0
.
Assume the swap gives back 200 token0.
Now, the contract holds:
210 (original) + 200 (swap) = 410 token0
110 (original) - 100 (swapped) = 10 token1
Step 4: Incorrect Fund Distribution
After the swap, the contract distributes funds:
User receives:
410 token0
StakeV2 (instead of the user) receives:
10 token1 (remaining token1Debt)
Expected Behavior:
The contract should have converted the full 110 token1 into token0
, ensuring the user gets their full expected amount.
Actual Issue:
Only 100 token1 was converted, leaving 10 token1 unconverted.
The unconverted 10 token1 is sent to StakeV2
instead of the user, resulting in a loss of funds.