Price Impact — Missing Slippage Check
A single-sided zapper executes a Uniswap V3 swap with amountOutMinimum hardcoded to zero and deadline set to block.timestamp. Any swap is open to sandwich attacks and executes at any price no matter how bad the rate.
~7 min read
Price impact is the change in exchange rate caused by your own trade. Slippage is the acceptable tolerance for that change. When amountOutMinimum = 0, the swap accepts any price — including a price that has been artificially worsened by a sandwich bot. This lab explores both organic price impact and MEV-extracted value.
1. Uniswap V3 and concentrated liquidity
Uniswap V3 concentrates liquidity in price ranges. A swap depletes liquidity from the current range and may cross into adjacent ranges — each with a different effective exchange rate. Large swaps relative to available liquidity have high price impact: the marginal price worsens with each unit of input token.
The exactInputSingle() function takes amountOutMinimum: the minimum acceptable output. If the actual output falls below this value, the transaction reverts. Setting it to 0 disables this protection entirely — the transaction will execute even if you receive 0 tokens of output.
The deadline parameter is a Unix timestamp after which the transaction must not execute. deadline = block.timestamp means the transaction executes in the current block regardless of how long it has been waiting in the mempool. A stale transaction from 30 minutes ago still executes at today's price — potentially after a large market move.
2. Two slippage failure modes
Organic slippage: your swap itself moves the price. For large trades relative to pool depth, the first tokens you get are at the pre-trade price, and later tokens get progressively worse rates as reserves shift. amountOutMinimum protects against organic slippage by reverting if the aggregate output is too low.
Adversarial slippage (sandwich): a bot pre-moves the price before your transaction arrives. Your transaction then executes at the adversarially worsened price. The bot post-corrects the price. With amountOutMinimum = 0, the bot can extract nearly the entire trade value without your transaction reverting.
3. The attack — sandwich on zero-slippage swap
The VulnerableZapper calls exactInputSingle() with amountOutMinimum = 0. Sandwich attack pseudocode:
// Zapper call: zapIn(10 WETH, feeTier=3000)
// Bot sees pending tx in mempool
// Front-run: bot buys poolToken with WETH, shifts pool price up
// Victim's zapIn executes: gets far fewer poolToken (price is now worse)
// amountOutMinimum = 0 → does not revert
// Back-run: bot sells poolToken back at original price, pockets spread
// Victim deposited into vault with severely diluted positionBeyond the sandwich: because deadline = block.timestamp, the transaction can be held in the mempool and included in a future block after a large market downturn. The user submits a zap expecting 100 poolTokens; a block later the price has dropped 20%; they receive 80 tokens with no protection.
Attack timeline
- 1
- 2
- 3
- 4
- 5
4. The fix — real amountOutMinimum and deadline
Pass amountOutMinimum computed from a current price quote or TWAP, and require the caller to provide a deadline in the future:
For protocol-owned zappers (e.g., keeper bots), compute minAmountOut server-side using a TWAP oracle: get the TWAP over 30 minutes and apply 0.5-1% slippage tolerance. Never use the current spot price as the reference for minAmountOut — that defeats the purpose since spot can be sandwiched.
For user-facing interfaces, the frontend computes minAmountOut from the current quote and a user-selected slippage tolerance (default 0.5%). The user should see a slippage warning for trades larger than 1% of pool depth.
5. What to look for as an auditor
- Any call to exactInputSingle, swapExactTokensForTokens, or similar with amountOutMin/amountOutMinimum = 0. Immediate finding.
- deadline = block.timestamp or deadline = 0. The deadline must be a future timestamp supplied by the caller (e.g., block.timestamp + 600 for 10 minutes). Block.timestamp as deadline is no protection.
- Contracts that auto-execute swaps without user input: keeper bots, auto-compounders, harvest calls. These are the highest-value sandwich targets because the attacker knows the exact time of execution.
- sqrtPriceLimitX96 = 0 in Uniswap V3 means no price limit on the swap path. This alone is not a bug but combined with amountOutMinimum = 0 it allows full pool traversal at any price.
- On-chain price computation for minAmountOut: if the contract computes minAmountOut from a current spot price on the same chain, the computation is manipulable. Use a TWAP or Chainlink feed.
- Multi-step zaps: if the function swaps twice (WETH → tokenA → tokenB), check slippage at each step. The second swap is also sandwichable if its amountOutMin is derived from the first swap's output.
With the two slippage failure modes in mind, open the Hunt tab. Find the exact parameter values that make the zapper exploitable, then describe the sandwich sequence.