/ Writing · · Updated · 6 min read
Slippage parameters in Solidity: what amountOutMin really protects
By Oussama Allamou · Solidity · DeFi · Slippage · DEX
Slippage is the difference between the amount a swap was quoted and the amount it actually pays out when it executes. A smart contract cannot know what price you saw, so you tell it the worst result you accept: a minimum output amount, usually called amountOutMin. If the swap would pay less than that, the whole transaction reverts.
Drag the tolerance and watch the floor move.
← Swap revertsSwap fills →
Floor · 1990
1900 DAIQuote · 2000 DAI
- amountOutMin
- 1990 DAI
- Loss you accept
- 10 DAI
Your swap fills as long as the pool pays at least 1990 DAI. The most you can lose against the quote is 10 DAI.
The example uses a quote of 2000 DAI. The floor is the quote minus your tolerance. Below the floor the swap reverts. Everything between the floor and the quote is loss you agreed to in advance.
What is slippage in DeFi?
An automated market maker prices a swap from the reserves in its pool at the moment the swap runs. The quote in your wallet was computed from the reserves a few seconds earlier. Between the two, the reserves can change. The gap between quoted output and executed output is slippage.
Take the example in the diagram. You want to swap 1 ETH and the interface quotes 2000 DAI. If the pool pays 1995 DAI when your transaction executes, you had 5 DAI of slippage. Slippage can also go your way: if the price moves in your favor, you receive more than the quote.
Why does the price move between quote and execution?
Three mechanisms, and they stack:
- Other trades land first. Your transaction waits to be included. Every swap on the same pool that executes before yours changes the reserves.
- The market moves. When the price changes elsewhere, arbitrage traders push the pool to the new price. If that happens while you wait, your quote is stale.
- Someone moves it on purpose. A sandwich bot sees your pending swap, buys before you to push the price up, lets your swap execute at the worse price, then sells right after. The room it has to work with is exactly the gap between the fair output and your floor.
If you want to see how a pending transaction gets reordered in the first place, the same mechanism drives the ERC-20 approval race. On Solana there is no public mempool, and the equivalent fight happens on the way to the leader, which I cover in how a Solana transaction lands.
That last point is why the tolerance matters. A loose floor is not only a risk of bad luck. It is the profit you are offering to anyone who can order transactions around yours.
How does amountOutMin work in a contract?
Here is one swap from quote to check. Step through it with the buttons, the arrow keys, or by tapping the diagram.
- Step 01Get a quote
The interface reads the pool reserves and tells you what 1 ETH buys right now. In this example the quote is 2000 DAI. A quote is a snapshot, not a promise.
- Step 02Set the floor
You pick a tolerance, here 0.5%. The interface turns it into a number: amountOutMin = 1990 DAI. That number is signed into your transaction. The contract never sees a percentage.
- Step 03Wait in line
Your transaction sits in the mempool until a block includes it. Other swaps on the same pool can land before yours, and each one moves the price.
- Step 04Execute at the real price
When your swap finally runs, the pool computes the output from its reserves at that moment. The quote you saw earlier plays no part in the math.
- Step 05Pass or revert
The router compares the real output with your floor. 1995 DAI is above 1990, so the swap settles. 1980 DAI is below it, so the whole transaction reverts: you keep your ETH and lose only the gas.
The contract does not handle percentages. The caller turns the tolerance into an amount before signing:
amountOutMin = quote * (10_000 - toleranceBps) / 10_000
With a quote of 2000 DAI and a tolerance of 50 basis points (0.5%), the floor is 1990 DAI. On a Uniswap V2 style router the check is one require at the end of the swap, and it fails with INSUFFICIENT_OUTPUT_AMOUNT.
If your own contract wraps a router, pass the caller's values through untouched:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
contract Swapper {
IUniswapV2Router02 public immutable router;
constructor(address _router) {
router = IUniswapV2Router02(_router);
}
/// The caller computes amountOutMin off-chain from a quote and a tolerance.
/// tokenIn must be transferred to this contract and approved for the router first (omitted here).
function swap(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 amountOutMin,
uint256 deadline
) external returns (uint256 amountOut) {
address[] memory path = new address[](2);
path[0] = tokenIn;
path[1] = tokenOut;
uint256[] memory amounts = router.swapExactTokensForTokens(
amountIn,
amountOutMin, // the floor: the router reverts below this
path,
msg.sender,
deadline // the router reverts after this timestamp
);
amountOut = amounts[amounts.length - 1];
}
}
The swap has two guards, and they cover different things. amountOutMin limits how bad the price can be. deadline limits how long the transaction stays valid, so a swap that was stuck for an hour cannot execute later at a price that happens to sit just above your floor.
Which mistakes leave a swap unprotected?
These are the ones I look for first when I read swap code:
- Hardcoding
amountOutMinto 0. The swap accepts any output. It works in every test and gets sandwiched in production. - Computing the floor on-chain in the same transaction. Calling
getAmountsOutand subtracting a percentage reads the pool after an attacker has already moved it. The floor moves with the manipulated price. The reference price has to come from outside the transaction: the caller, or an oracle. - Setting the deadline to
block.timestamp. That is always "now" at execution time, so the check can never fail. The deadline has to be chosen by the caller before signing. - Dropping the parameters in a wrapper. A vault or zap function that swaps internally still needs a floor, passed in by the caller or derived from an oracle.
- Checking the wrong step. In a multi-hop route or a swap followed by a deposit, check the final amount the user receives, not an intermediate one.
What do the terms mean?
- Slippage: the difference between the quoted output of a trade and the executed output.
- Slippage tolerance: the largest loss against the quote you accept, usually given as a percentage or in basis points.
- amountOutMin: the tolerance converted into a token amount. The swap reverts if the output is below it. For swaps with a fixed output the mirror parameter is
amountInMax. - Deadline: a timestamp after which the transaction reverts instead of executing.
- Price impact: the price change caused by your own trade's size against the pool. It is already included in the quote. Slippage is what changes after the quote.
- Sandwich attack: a pair of trades placed right before and right after yours to capture the room your tolerance leaves.
FAQ
What slippage tolerance should I set?
There is no universal number. A deep pool of two stable assets barely moves, so a tight tolerance rarely fails. A thin or volatile pool needs more room or the swap keeps reverting. The rule is the same in both cases: use the tightest value that still lets your swap land, because everything above that is room you offer to a sandwich.
Is slippage the same as price impact?
No. Price impact comes from the size of your own trade and is known at quote time. Slippage is the change between the quote and the execution, caused by other transactions and market movement.
What happens when a swap fails the slippage check?
The transaction reverts. The swap is undone and your input tokens never leave your wallet. You still pay the gas the transaction used up to the revert.
Why is amountOutMin = 0 dangerous?
It tells the contract that any output is acceptable. A bot that sees the pending swap can move the price as far as the pool allows, let your swap execute, and reverse its position. Nothing in your transaction stops it.
Can a contract compute the minimum output itself?
Only from a price that an attacker cannot move in the same transaction, such as a well-built oracle. Reading the pool's own spot price inside the swap transaction gives you the manipulated price, so the check always passes.
Further reading
- Uniswap V2 Router02 reference: the exact signature and revert reasons of
swapExactTokensForTokens. - Solidity documentation: language reference.