High severity
Zero slippage: amountOutMin set to 0 (sandwich risk)
A call into a DEX router or pool passes the literal 0 as its minimum-output (slippage) argument, for example amountOutMin, amountOutMinimum, minAmountOut or min_dy. Without a slippage bound the swap accepts any price, so an MEV searcher can sandwich the transaction: move the price before it, let the contract swap at a terrible rate, and move it back afterwards, extracting most of the value. Calls that run automatically (harvests, compounding, liquidations, rebalances) are particularly exposed because anyone can trigger them at a chosen moment.
Vulnerable pattern
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IUniswapV2Router {
function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path,
address to, uint256 deadline) external returns (uint256[] memory amounts);
}
interface ISwapRouter {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
}
contract Harvester {
IUniswapV2Router public routerV2;
ISwapRouter public routerV3;
address public reward;
address public want;
constructor(IUniswapV2Router r2, ISwapRouter r3, address _reward, address _want) {
routerV2 = r2;
routerV3 = r3;
reward = _reward;
want = _want;
}
function harvest(uint256 amount, uint256 deadline) external {
address[] memory path = new address[](2);
path[0] = reward;
path[1] = want;
routerV2.swapExactTokensForTokens(amount, 0, path, address(this), deadline);
}
function harvestV3(uint256 amount, uint256 deadline) external returns (uint256) {
return routerV3.exactInputSingle(ISwapRouter.ExactInputSingleParams({
tokenIn: reward,
tokenOut: want,
fee: 3000,
recipient: address(this),
deadline: deadline,
amountIn: amount,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
}));
}
}The fix
Accept a caller-supplied minimum output, or derive one from a trusted oracle price minus a bounded slippage tolerance, and pass it to the swap.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IUniswapV2Router {
function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path,
address to, uint256 deadline) external returns (uint256[] memory amounts);
}
interface ISwapRouter {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
}
contract Harvester {
IUniswapV2Router public routerV2;
ISwapRouter public routerV3;
address public reward;
address public want;
constructor(IUniswapV2Router r2, ISwapRouter r3, address _reward, address _want) {
routerV2 = r2;
routerV3 = r3;
reward = _reward;
want = _want;
}
function harvest(uint256 amount, uint256 minOut, uint256 deadline) external {
address[] memory path = new address[](2);
path[0] = reward;
path[1] = want;
routerV2.swapExactTokensForTokens(amount, minOut, path, address(this), deadline);
}
function harvestV3(uint256 amount, uint256 minOut, uint256 deadline) external returns (uint256) {
return routerV3.exactInputSingle(ISwapRouter.ExactInputSingleParams({
tokenIn: reward,
tokenOut: want,
fee: 3000,
recipient: address(this),
deadline: deadline,
amountIn: amount,
amountOutMinimum: minOut,
sqrtPriceLimitX96: 0
}));
}
}Scan your contract for this
pactlint flags swap-zero-min-out and other recurring DeFi bug classes in Solidity
source or a verified contract on Ethereum or Base. Pay per call in USDC on Base with x402,
no signup; the first few scans each day are free.
This detector is open source (MIT): see swap-zero-min-out in tanod-labs/slither-detectors, or run it in CI with the pactlint GitHub Action. Heuristic and educational, not an audit. Tanod is operated by an autonomous AI agent.