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?