🎓 A1Learner
🔍
0 of 45 lessons completed

Solidity Frontrunning Defenses

A frontrunning defense is a guard that stops a trade from settling at a bad price. A minimum-out amount and a deadline are two such guards.

A minimum-out amount blocks a bad price

A trade runs first and moves the price. The victim swap then reverts instead of settling for less than its minimum.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract ProtectedSwap {
    uint256 public reserveA = 100;
    uint256 public reserveB = 100;

    function swap(uint256 amountIn, uint256 minOut, uint256 deadline) public returns (uint256) {
        require(block.timestamp <= deadline, "deadline passed");
        uint256 amountOut = reserveB * amountIn / (reserveA + amountIn);
        require(amountOut >= minOut, "below minimum out");
        reserveA += amountIn;
        reserveB -= amountOut;
        return amountOut;
    }
}

A deadline blocks a stale swap

Time moves past the deadline. The same swap then reverts, even though the price is still fine.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract ProtectedSwap {
    uint256 public reserveA = 100;
    uint256 public reserveB = 100;

    function swap(uint256 amountIn, uint256 minOut, uint256 deadline) public returns (uint256) {
        require(block.timestamp <= deadline, "deadline passed");
        uint256 amountOut = reserveB * amountIn / (reserveA + amountIn);
        require(amountOut >= minOut, "below minimum out");
        reserveA += amountIn;
        reserveB -= amountOut;
        return amountOut;
    }
}

A fair swap still succeeds

Both guards pass when the price holds and the deadline has not passed. The swap returns the expected amount.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract ProtectedSwap {
    uint256 public reserveA = 100;
    uint256 public reserveB = 100;

    function swap(uint256 amountIn, uint256 minOut, uint256 deadline) public returns (uint256) {
        require(block.timestamp <= deadline, "deadline passed");
        uint256 amountOut = reserveB * amountIn / (reserveA + amountIn);
        require(amountOut >= minOut, "below minimum out");
        reserveA += amountIn;
        reserveB -= amountOut;
        return amountOut;
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract ProtectedSwap {
    uint256 public reserveA = 100;
    uint256 public reserveB = 100;

    function swap(uint256 amountIn, uint256 minOut, uint256 deadline) public returns (uint256) {
        require(block.timestamp <= deadline, "deadline passed");
        uint256 amountOut = reserveB * amountIn / (reserveA + amountIn);
        require(amountOut >= minOut, "below minimum out");
        reserveA += amountIn;
        reserveB -= amountOut;
        return amountOut;
    }
}

Key points

  • Minimum-out: the smallest amount out a trader will accept for a swap.
  • Use a minimum-out amount to make a swap revert instead of settling at a worse price.
  • Deadline: the last block time at which a swap may still run.
  • Use a deadline to make a stale swap revert instead of running long after it was signed.
  • Private order flow: sending a trade through a private channel so it is not seen before it runs.

In this example, a first swap moves the price, a victim swap with a minimum of 9 reverts, and the same victim swap with a minimum of 7 succeeds and returns 7.

Note

a minimum-out amount guards the price, and a deadline guards the timing, and neither guard changes the pool price itself.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

A swap would return 7 tokens, but its minimum-out amount is 9. What happens?