#41432 [SC-High] Attacker can DoS `StakeV2`'s rewards distribution by repeatedly inflating Zapper's approval for whitelisted Kodiak Vault tokens
Submitted on Mar 15th 2025 at 06:23:09 UTC by @merlinboii for Audit Comp | Yeet
Report ID: #41432
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/contracts/Zapper.sol
Impacts:
Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Permanent freezing of unclaimed yield
Description
Brief/Intro
An attacker can repeatedly inflate the Zapper's approval for any whitelisted Kodiak Vault tokens that are targeted for distribution as StakeV2
rewards. This leads to a Denial-of-Service (DoS) on rewards distribution due to an overflow revert in safeIncreaseAllowance()
, without incurring any direct cost other than gas fees.
Vulnerability Details
The Zapper contract increases its allowance for Kodiak Vault tokens based on user-specified amount0Max
and amount1Max
values. However, these amounts are not necessarily fully utilized when adding liquidity, causing excess approvals to accumulate indefinitely.
Over time, this can inflate the allowance to uint256.MAX
(or a nearest value), leading to an overflow revert in operation that include calling safeIncreaseAllowance()
.
Note: The attack steps are described in the Proof of Concept section.
The actual amount of tokens used is computed within IslandRouter._addLiquidity()
, where amount0In
and amount1In
are determined based on the minimum amount from _computeMintAmounts()
.
Impact Details
The
StakeV2
reward distribution suffers from a Denial-of-Service (DoS) vulnerability, preventing rewards from being distributed, which results in the permanent freezing of unclaimed yield.The Zapper cannot execute
_yeetIn
for certain tokens that have undergone inflation in approval amounts.
Although the Kodiak Router can be replaced with a new one, the attack can be repeated on the new router, as the logic still allows it.
Moreover, in the rare case that the Kodiak Vault settings in the Zapper contract become malicious, an attacker could potentially pull any funds from the Zapper (if funds are available).
References
https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/contracts/Zapper.sol#L507-L508 https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/StakeV2.sol#L153-L180 https://github.com/immunefi-team/audit-comp-yeet/blob/main/src/StakeV2.sol#L182-L201
Link to Proof of Concept
https://gist.github.com/merlinboii/90dfcadc9170a55a3e6b39555a4e0461
Proof of Concept
Proof of Concept
Here is the runnable PoC: https://gist.github.com/merlinboii/90dfcadc9170a55a3e6b39555a4e0461 Result Logs:
Below is the step-by-step conceptual PoC: This PoC uses YEET-WBERA KodiakIsland (0xEc8BA456b4e009408d0776cdE8B91f8717D13Fa1) on Berachain Mainnet to demonstrate how a user can inflate Zapper’s approval allowance without spending full amounts.
StakeV2
Rewards SystemThe LP rewards for YEET-WBERA Kodiak Island consist of:
token0
: YEETtoken1
: WBERA
The
StakeV2
contract contains the functions:executeRewardDistributionYeet()
, which distributes YEET rewards.executeRewardDistribution()
, which distributes BERA rewards.
These functions call
_yeetIn()
to add liquidity to the YEET-WBERA Kodiak Vault and deposit LP rewards into Trifecta Vaults.
Attacker Target: DoS the Reward Distribution and Feasibility
Approach for DoS: Inflate the Zapper's approval allowance for
YEET
orWBERA
touint256(MAX)
, causing an overflow revert whensafeIncreaseAllowance()
is executed before liquidity is added.
@> token0.safeIncreaseAllowance(address(kodiakStakingRouter), stakingParams.amount0Max); @> token1.safeIncreaseAllowance(address(kodiakStakingRouter), stakingParams.amount1Max); // add liquidity using KodiakStakingRouter return kodiakStakingRouter.addLiquidity( IKodiakVaultV1(kodiakVault), stakingParams.amount0Max, stakingParams.amount1Max, stakingParams.amount0Min, stakingParams.amount1Min, stakingParams.amountSharesMin, stakingParams.receiver ); } ```
Approach for Attack Feasibility: Target Zapper functions that do not require transferring full
stakingParams.amount0Max
orstakingParams.amount1Max
before executing_yeetIn()
:zapInToken0()
: TransfersstakingParams.amount0Max + swapData.inputAmount
and allows arbitrary input forstakingParams.amount1Max
.zapInToken1()
: TransfersstakingParams.amount1Max + swapData.inputAmount
and allows arbitrary input forstakingParams.amount0Max
.zapIn()
: Transfers theinputToken
amount and allows arbitrary input forstakingParams.amount0Max
andstakingParams.amount1Max
.Since the
addLiquidity()
process caps minting at the minimum liquidity result calculated from bothamount0Max
andamount1Max
, the arbitrary inputs will not affect the actual LP minting results.
Attacker calls
Zapper.zapInToken0()
. The user sets an artificially high approval ofstakingParams.amount1Max
for Kodiak Vault tokens (YEET
andWBERA
):
The process triggers to increase allowance up to
amount0Max: 1e18
amount1Max: 7719472615821079694904732333912527190217998977709370935963838933860875309329
.
We can see the actual of
amount0In
,amount1In
andmintAmount
from that simulate block (Block: 2275117
) by reading fromYEET-WBERA-KodiakIsland.getMintAmounts()
:
As from the process of
IslandRouter._addLiquidity()
, it will only transfer999999999999999986
YEET and3461048089839568
WBERA to the router contract for further deposit operation (minting LP).
The allowance of
Zapper
forKodiakStakingRouter
will not reset to 0 and remains:
YEET: 1e18 - 999999999999999986
WBERA: 7719472615821079694904732333912527190217998977709370935963838933860875309329 - 3461048089839568 = ~uint256(MAX) / 15 times
The attacker can repeat the process until the allowance reaches
uint256.MAX
(or the nearest value), causing an overflow revert whenever the target token executessafeIncreaseAllowance()
on the Zapper for a Kodiak router.The attacker receives LP tokens along with unused
YEET
andWBERA
from providing liquidity. This means the attacker only incurs gas costs for execution.When
StakeV2.executeRewardDistributionYeet()
orStakeV2.executeRewardDistribution()
is executed, and theoldAllowance
(inflated to ~uint256.MAX
) plusvalue
(stakingParams.amountXMax
) exceedsuint256.MAX
, it triggers an overflow revert:
Final Stage: Denial-of-Service (DoS) to StakeV2 Reward Distribution process via Zapper Approval Inflation
The attacker inflates the Zapper’s approval for YEET
and WBERA
on the KodiakStakingRouter
, causing a DoS condition by making further approvals impossible.
Was this helpful?