Section 5 of 8

Oracle Manipulation Risks

Understanding Automated Market Makers

Traditional exchanges use order books where buyers and sellers place limit orders at specific prices. A matching engine pairs them up. This works well for liquid markets, but in crypto, many token pairs suffer from thin order books and low liquidity.

Automated Market Makers (AMMs) solve this problem by replacing the order book with a mathematical formula that determines the price automatically. Instead of matching counterparties, users trade directly against a liquidity pool, a smart contract holding reserves of two tokens.

The Constant Product Formula

Uniswap V2 popularized the simplest AMM formula: the constant product. It states that the product of the two reserve balances must remain constant after every trade:

x * y = k

Where x is the reserve of token A, y is the reserve of token B, and k is a constant. When a trader adds some amount of token A to the pool, the contract calculates how much token B to send back such that the product stays the same.

This creates a smooth price curve: as one reserve decreases, its price rises exponentially relative to the other token. Large trades move the price more than small trades, which is a natural property called price impact.

SimpleAMM.solsolidity
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.20;
3
4/// @title SimpleAMM
5/// @notice Minimal constant product AMM for educational purposes.
6contract SimpleAMM {
7    uint256 public reserveA;
8    uint256 public reserveB;
9
10    /// @notice The constant product invariant: reserveA * reserveB = k
11    /// After every swap, the product must remain equal to (or greater than) k.
12    function swap(uint256 amountAIn, uint256 amountBIn)
13        external
14        returns (uint256 amountOut)
15    {
16        require(
17            (amountAIn > 0) != (amountBIn > 0),
18            "Exactly one input required"
19        );
20
21        if (amountAIn > 0) {
22            // Selling token A for token B
23            uint256 newReserveA = reserveA + amountAIn;
24            uint256 newReserveB = (reserveA * reserveB) / newReserveA;
25            amountOut = reserveB - newReserveB;
26
27            reserveA = newReserveA;
28            reserveB = newReserveB;
29        } else {
30            // Selling token B for token A
31            uint256 newReserveB = reserveB + amountBIn;
32            uint256 newReserveA = (reserveA * reserveB) / newReserveB;
33            amountOut = reserveA - newReserveA;
34
35            reserveA = newReserveA;
36            reserveB = newReserveB;
37        }
38    }
39}

How the Swap Works

Look at the swap function above. When a user sends amountAIn tokens, the contract calculates the new reserve of token A by adding the input. Then it derives the new reserve of token B using the invariant (reserveA * reserveB = k). The difference between the old and new reserveB is the output amount sent to the trader.

This is elegant but simplified. A production AMM must also account for trading fees (typically 0.3%), rounding errors, flash loan guards, and reentrancy protection.

What Goes Wrong

The simplified AMM above is missing a critical safeguard: slippage protection. Without aminAmountOut parameter, a frontrunner can sandwich the transaction.

The attack works like this: the attacker sees a pending swap in the mempool, buys the target token first (pushing the price up), lets the victim's trade execute at the worse price, then sells immediately after. The victim receives fewer tokens than expected, and the attacker pockets the difference.

  • Always require a minAmountOut parameter in swap functions.
  • Use a deadline parameter to prevent stale transactions from executing.
  • Consider private mempools or commit/reveal schemes for high-value trades.

Knowledge Check

Answer correctly to earn lynx

In the constant product formula x * y = k, what happens to the price of token B when someone buys a large amount of token B from the pool?