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

A minimal contract with the bug
// 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.

The same contract, corrected
// 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.

How to scan →

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.