#41528 [SC-High] When claiming rewards in native Bera via `StakeV2.claimRewardsInNative`, excess `token0Debt` or/and `token1Debt` is not returned to the kodiak vault but stuck in `StakeV2` contract.
Submitted on Mar 16th 2025 at 09:46:33 UTC by @X0sauce for Audit Comp | Yeet
Report ID: #41528
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/StakeV2.sol
Impacts:
Permanent freezing of unclaimed yield
Description
Vulnerability Details
When claiming rewards in the form of native Bera tokens through StakeV2.claimRewardsInNative, any surplus unused token0Debt and token1Debt is not sent back to the kodiak vault. Instead, it is returned to the StakeV2 contract, where it becomes stuck and cannot be utilized for future reward claims.
In StakeV2.claimRewardsInNative
function claimRewardsInNative(
uint256 amountToWithdraw,
IZapper.SingleTokenSwap calldata swapData0,
IZapper.SingleTokenSwap calldata swapData1,
IZapper.KodiakVaultUnstakingParams calldata unstakeParams,
IZapper.VaultRedeemParams calldata redeemParams
) external nonReentrant {
_updateRewards(msg.sender);
IZapper.VaultRedeemParams memory updatedRedeemParams = _verifyAndPrepareClaim(amountToWithdraw, redeemParams);
IERC20(redeemParams.vault).approve(address(zapper), amountToWithdraw);
@> uint256 receivedAmount =
zapper.zapOutNative(msg.sender, swapData0, swapData1, unstakeParams, updatedRedeemParams); //@audit zaps out of ERC4626 vaultto native Bera Token to be given to staker as rewards
emit Claimed(msg.sender, receivedAmount);
}In Zapper.zapOutNative
In Zapper._yeetOut
In Zapper_approveAndUnstakeFromKodiakVault
Going back to Zapper.zapOutNative
In Zapper._swapToWBERA
Root Cause
Lack of mechanism in place to return any unused surplus of token0 or token1 to the Kodiak vault.
Impact
Any surplus of token0 or token1 remains stuck in StakeV2 and cannot be utilized for subsequent reward claims.
Mitigation
Implement a mechasim to return the surplus token0Debt or token1Debt back to the Kodiak vaults for future reward claims.
Proof of Concept
POC
Consider the below simplistic scenario
Bob, a staker, initiates a call to
StakeV2.claimRewardsInNativeto claim rewards equivalent to 100 vault shares.He proceeds to call
zapper.zapOutToToken0, which leads him tozapper._yeetOut.During this process, he calls
zapper._withdrawFromVaultand receives 10islandTokensin exchange for the 100 vault shares redeemed from the ERC4626 vault.The
redeemParams.receiveris designated as theZapperaddress, allowing Bob to enterZapper._approveAndUnstakeFromKodiakVaultto redeem the underlyingtoken0andtoken1in exchange for the island tokens. In this process, he receives:_token0= Wbera_token1= USDC (the specific token type is not critical)_amount0= 10_amount1= 10
Afterwards, he returns to
Zapper.zapOutNativeand callsZapper._swapToWBERAwith the following parameters:token0=WBERAtoken1=USDCtoken0Debt=_token0= 10token1Debt=_token1= 10
Suppose the
Zapper._swapToWBERAfunction is executed with a vault wheretoken0isWBERA, and the subsequent call result in:Only 5 out of the 10
token1(USDC) utilized to swap for 0.5WBERA.The remaining
token1Debt= 10 - 5 = 5USDCwill be sent toStakeV2and become stuck instead of being returned to the Kodiak vault, where it could be used for future reward claims.The total
wberaDebt= 10 + 0.5 = 10.5WBERA.
Ultimately, the total
wberaDebtof 10.5WBERAis transferred to Bob, while 5USDCremains trapped inStakeV2.
A similar scenario can happen if token1 is the WBERA token
Was this helpful?