#42158 [SC-High] Users can DoS `Zapper::zapIn` functionality for a token
Submitted on Mar 21st 2025 at 11:29:11 UTC by @kmm for Audit Comp | Yeet
Report ID: #42158
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)
Description
Brief/Intro
In certain edge cases involving Kodiak Island's liquidity pools, specifically when totalSupply
is 0 or when there is only one-sided liquidity, a user can exploit the safeIncreaseAllowance
mechanism used by the zapper. This allows the user to perform a Denial-of-Service (DoS) attack on the router's approval functionality, effectively blocking any future deposit attempts via the Kodiak router for that specific token.
Vulnerability Details
Kodiak Island calculates mint share rates using the following logic:
function getMintAmounts(uint256 amount0Max, uint256 amount1Max) external view returns (uint256 amount0, uint256 amount1, uint256 mintAmount) {
uint256 totalSupply = totalSupply();
if (totalSupply > 0) {
(amount0, amount1, mintAmount) = _computeMintAmounts(totalSupply, amount0Max, amount1Max);
} else {
(uint160 sqrtRatioX96,,,,,,) = pool.slot0();
uint128 newLiquidity = LiquidityAmounts.getLiquidityForAmounts(sqrtRatioX96, lowerTick.getSqrtRatioAtTick(), upperTick.getSqrtRatioAtTick(), amount0Max, amount1Max);
mintAmount = uint256(newLiquidity);
(amount0, amount1) = LiquidityAmounts.getAmountsForLiquidity(sqrtRatioX96, lowerTick.getSqrtRatioAtTick(), upperTick.getSqrtRatioAtTick(), newLiquidity);
}
}
function _computeMintAmounts(uint256 totalSupply, uint256 amount0Max, uint256 amount1Max) private view returns (uint256 amount0, uint256 amount1, uint256 mintAmount) {
(uint256 amount0Current, uint256 amount1Current) = getUnderlyingBalances();
if (amount0Current == 0 && amount1Current > 0) {
mintAmount = FullMath.mulDiv(amount1Max, totalSupply, amount1Current);
} else if (amount1Current == 0 && amount0Current > 0) {
mintAmount = FullMath.mulDiv(amount0Max, totalSupply, amount0Current);
} else if (amount0Current == 0 && amount1Current == 0) {
revert("");
} else {
uint256 amount0Mint = FullMath.mulDiv(amount0Max, totalSupply, amount0Current);
uint256 amount1Mint = FullMath.mulDiv(amount1Max, totalSupply, amount1Current);
require(amount0Mint > 0 && amount1Mint > 0, "mint 0");
mintAmount = amount0Mint < amount1Mint ? amount0Mint : amount1Mint;
}
amount0 = FullMath.mulDivRoundingUp(mintAmount, amount0Current, totalSupply);
amount1 = FullMath.mulDivRoundingUp(mintAmount, amount1Current, totalSupply);
}
When totalSupply == 0
, the mint amount directly correlates to the liquidity minted from the provided token amounts. In the case of one-sided liquidity, the vault only pulls one of the token amounts.
The zapper's _yeetIn
function sets token allowances to the router using safeIncreaseAllowance
for both amount0Max
and amount1Max
, and then invokes addLiquidity
. However, the zapInNative
function does not validate amount0Max
or amount1Max
. This opens the door for an attacker to set a massive allowance, such as type(uint256).max
, without actually spending the tokens.
In zero-supply situations, an attacker can manipulate the pool state (e.g., by shifting it outside of tickLower
or tickUpper
) to ensure one of the token amounts calculated by getAmountsForLiquidity
returns zero. This causes the vault to pull only the other token, allowing the attacker to set an inflated approval for the unused token.
For pools with one-sided liquidity, this same effect occurs without the need to push the pool outside any tick range, as one of the amounts will inherently be zero.
Impact Details
This vulnerability enables an attacker to push the router's token allowance to near type(uint256).max
without spending it, leveraging the behavior of safeIncreaseAllowance
. As a result, future attempts to increase the allowance for that token will fail, effectively blocking zapper deposits for that token. This creates a persistent DoS condition for the affected token within the Kodiak Island ecosystem.
References
Proof of Concept
Proof of Concept
totalSupply=0
The vault is added with
totalSupply=0
The user sees this and in one transaction
Pushes the price outside of the range in the island
Calls
zapInNative
, and passesamount1Max
astype(uint256).max
The island only pulls
amount0
leaving the approval attype(uint256).max
Any further attempt at depositing this token, will revert due to overflow in
safeIncreaseAllowance
.
Was this helpful?