How a Constant-Product AMM Works: Reserves, Fees, and Slippage

Understand constant-product AMMs through reserves, fee-adjusted output, price impact, slippage tolerance, and the limits of toy simulators.

· · 9 min read

How a Constant-Product AMM Works: Reserves, Fees, and Slippage

An automated market maker does not wait for a matching order. A liquidity pool quotes a swap from its reserves and a formula. That sounds simple until you account for fees, token direction, rounding, callbacks, and the minimum output a user is willing to accept.

The constant-product idea

For a simplified pool:

~text
x × y = k
~

If a pool holds 100 ETH and 200,000 USDC, the reserve ratio suggests a spot price of 2,000 USDC per ETH. A swap changes both reserves, so the trader does not receive every unit at that starting price.

For the Uniswap v2 fee model, the output is commonly written as:

~text
amountInWithFee = amountIn × 997
amountOut = amountInWithFee × reserveOut /
(reserveIn × 1000 + amountInWithFee)
~

This is a model for understanding the calculation, not a replacement for reading the contract.

A small, testable function

~python
def get_amount_out(amount_in, reserve_in, reserve_out):
if amount_in <= 0 or reserve_in <= 0 or reserve_out <= 0:
raise ValueError("amounts and reserves must be positive")
amount_in_with_fee = amount_in * 997
return (amount_in_with_fee * reserve_out) / (
reserve_in * 1000 + amount_in_with_fee
)
~

With 2,000 USDC entering the example pool, the simplified result is about 0.987158 ETH. The effective price is roughly 2,026 USDC per ETH, worse than the starting ratio because of price impact and the fee.

Price impact is not slippage tolerance

Price impact is the movement caused by the size of the trade relative to the pool. Slippage tolerance is the user's maximum acceptable difference before the transaction reverts. A loose tolerance does not create a better quote; it allows a worse execution to succeed.

Compare the trade with the pool depth, set a minimum output, and inspect the route before signing. A private transaction route may change who can see the transaction before inclusion, but it introduces another service and does not guarantee a good execution.

What the production contract adds

Uniswap v2 checks balances after transfers, derives the actual input, applies the fee-adjusted invariant, and handles callbacks and a reentrancy lock. Token behaviour can also be unusual: transfer fees, rebasing, decimals, and callback logic change the assumptions of a toy simulator.

Never copy a floating-point Python example into a token contract. Production Solidity uses integer arithmetic, explicit rounding, safe bounds, and tests against the exact protocol implementation.

Liquidity providers take risk too

Swap fees accrue to the pool, but a liquidity provider is exposed to price changes between the deposited assets, impermanent loss, smart-contract bugs, token risk, and MEV. Concentrated-liquidity designs can use capital more efficiently while requiring active range management.

The useful mental model is modest: reserves determine the quote, the trade changes the reserves, and every shortcut in the model needs an assumption next to it.

References

• Uniswap v2 Core whitepaper
• Uniswap v2 Pair contract
• Uniswap documentation