Solidity AMM Math in Depth
An automated market maker is a pool that prices a swap from its own token reserves instead of from an order book. Its constant product rule keeps the product of the two reserves fixed while a swap moves the ratio.
The constant product formula gives the exact swap output
With reserves of 1000 and 1000, swapping in 100 returns 90. The formula is y times dx divided by x plus dx.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract AmmMath {
function quoteOut(uint256 dx, uint256 x, uint256 y) public pure returns (uint256) {
return (y * dx) / (x + dx);
}
function quoteOutWithFee(uint256 dx, uint256 x, uint256 y) public pure returns (uint256) {
uint256 dxAfterFee = (dx * 997) / 1000;
return (y * dxAfterFee) / (x + dxAfterFee);
}
}
A fee shrinks the input before the formula runs
A 0.3 percent fee turns an input of 100 into 99 before the formula runs. The fee version returns 180 where the fee-free version returns 181.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract AmmMath {
function quoteOut(uint256 dx, uint256 x, uint256 y) public pure returns (uint256) {
return (y * dx) / (x + dx);
}
function quoteOutWithFee(uint256 dx, uint256 x, uint256 y) public pure returns (uint256) {
uint256 dxAfterFee = (dx * 997) / 1000;
return (y * dxAfterFee) / (x + dxAfterFee);
}
}
A second swap gets a worse price than the first
The first swap moves the reserves, so the second swap starts from a worse ratio. The same input of 100 returns 90 first and only 75 second.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract AmmPool {
uint256 public reserveX;
uint256 public reserveY;
constructor(uint256 x, uint256 y) {
reserveX = x;
reserveY = y;
}
function swap(uint256 dx) public returns (uint256 dy) {
dy = (reserveY * dx) / (reserveX + dx);
reserveX += dx;
reserveY -= dy;
}
}
Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract AmmPoolWithFee {
uint256 public reserveX;
uint256 public reserveY;
constructor(uint256 x, uint256 y) {
reserveX = x;
reserveY = y;
}
function swap(uint256 dx) public returns (uint256 dy) {
uint256 dxAfterFee = (dx * 997) / 1000;
dy = (reserveY * dxAfterFee) / (reserveX + dxAfterFee);
reserveX += dx;
reserveY -= dy;
}
}
Key points
- AMM: An automated market maker is a pool that prices swaps from its own reserves.
- Constant product: The constant product rule keeps x times y equal to a fixed number k.
- Swap output: A swap of dx tokens in returns dy equal to y times dx divided by x plus dx.
- Fee: A fee reduces the input first, so the output is computed from the smaller input.
- Slippage: Slippage is the worse average price a large swap pays compared with a small swap.
- Restatement: A pool prices every trade from its reserves, and each trade moves those reserves.
In this example, the pool starts with reserves of 1000 and 2000. A swap of 100 first reduces the input to 99 by the fee, then applies the constant product formula to give 180 out. The reserves update to 1100 and 1820, so a second swap of 100 starts from a worse ratio and returns less.
Note
Integer division truncates, so the contract output is the exact truncated value, not a rounded decimal.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Remix IDE
Exercise
Reserves are 1000 and 1000. What does a swap of 100 tokens in return before any fee?