Разработка DEX на базе AMM
AMM (Automated Market Maker) — это протокол, который заменяет традиционный order book на математическую формулу. Вместо того чтобы ждать контрагента с противоположным ордером, трейдер торгует против пула ликвидности. Цена определяется алгоритмом в реальном времени. Это изменило DeFi — и понять, как это работает изнутри, критично перед тем как строить собственный DEX.
Математика ценообразования
Constant Product Formula
Uniswap V2 popularized формулу x * y = k, где x и y — резервы двух токенов в пуле, k — инвариант. При любой операции произведение резервов должно оставаться константой (с поправкой на комиссию).
Текущая цена токена X в единицах Y: price = y / x
При свопе: трейдер вносит Δx токена X, получает Δy токена Y:
(x + Δx) * (y - Δy) = k
Δy = y - k / (x + Δx) = y * Δx / (x + Δx)
С учётом комиссии 0.3% (fee = 0.003):
Δy = y * Δx * (1 - fee) / (x + Δx * (1 - fee))
Price impact — насколько сдвигается цена при сделке — прямо пропорционален размеру сделки относительно резервов пула. Это фундаментальное свойство: большие ордера дают плохую цену в малых пулах.
Concentrated Liquidity (Uniswap V3)
Uniswap V3 добавил concentrated liquidity: LP-провайдеры указывают ценовой диапазон [Pa, Pb], в котором работает их ликвидность. Вне диапазона ликвидность "неактивна" и не получает комиссии.
Формулы становятся сложнее. Реальные резервы X и Y в пуле:
x = L * (1/√P - 1/√Pb)
y = L * (√P - √Pa)
Где L — количество ликвидности (виртуальная константа), P — текущая цена.
Это фундаментально меняет экономику: капитальная эффективность LP возрастает в 100-4000 раз по сравнению с V2 для стейблкоин-пар, но LP принимает на себя более сложный impermanent loss профиль.
Curve StableSwap
Для активов с близкой ценой (stablecoins, stETH/ETH) constant product даёт плохой slippage. Curve использует гибридную формулу, комбинирующую constant product и constant sum:
A * n^n * Σxi + D = A * D * n^n + D^(n+1) / (n^n * Πxi)
Где A — amplification coefficient. При A=0 — обычный constant product. При A→∞ — constant sum (нулевой slippage, но нестабильно). Curve балансирует между ними, обеспечивая минимальный slippage в диапазоне около паритета и сходясь к constant product на краях.
Архитектура смарт-контрактов
Core контракты
Типичная архитектура AMM DEX состоит из нескольких уровней:
Factory контракт — реестр и создатель пулов:
contract AmmFactory {
mapping(address => mapping(address => address)) public getPair;
address[] public allPairs;
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
function createPair(address tokenA, address tokenB)
external returns (address pair)
{
require(tokenA != tokenB, "IDENTICAL_ADDRESSES");
(address token0, address token1) = tokenA < tokenB
? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), "ZERO_ADDRESS");
require(getPair[token0][token1] == address(0), "PAIR_EXISTS");
bytes memory bytecode = type(AmmPair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IAmmPair(pair).initialize(token0, token1);
getPair[token0][token1] = pair;
getPair[token1][token0] = pair;
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}
}
CREATE2 важен: адрес пула детерминирован и предсказуем из адресов токенов, это позволяет Router-у вычислять адрес пула без обращения к Factory.
Pair (Pool) контракт — ядро AMM. Хранит резервы, выпускает LP-токены, исполняет свопы:
function swap(
uint amount0Out,
uint amount1Out,
address to,
bytes calldata data
) external lock {
require(amount0Out > 0 || amount1Out > 0, "INSUFFICIENT_OUTPUT_AMOUNT");
(uint112 _reserve0, uint112 _reserve1,) = getReserves();
// Оптимистичный перевод — сначала отдаём
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
// Проверяем что баланс сошёлся
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, "INSUFFICIENT_INPUT_AMOUNT");
// Проверка инварианта (с учётом комиссии 0.3%)
uint balance0Adjusted = balance0 * 1000 - amount0In * 3;
uint balance1Adjusted = balance1 * 1000 - amount1In * 3;
require(balance0Adjusted * balance1Adjusted >= uint(_reserve0) * _reserve1 * 1000**2, "K");
_update(balance0, balance1, _reserve0, _reserve1);
}
Паттерн "оптимистичный перевод + проверка инварианта" также является основой flash loans: data.length > 0 позволяет вызвать callback на получателе до проверки — это и есть flash swap.
Router контракт — удобный интерфейс для конечных пользователей. Обрабатывает multi-hop маршруты (A→B→C), slippage protection, deadline.
LP-токены и управление ликвидностью
При добавлении ликвидности LP получает ERC-20 токены, представляющие долю пула:
LP_minted = totalSupply * min(amount0 / reserve0, amount1 / reserve1)
При первом добавлении: LP_minted = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY
MINIMUM_LIQUIDITY = 1000 — эти LP-токены навсегда сжигаются при создании пула, предотвращая атаку на пустой пул.
Оракулы цен
TWAP (Time-Weighted Average Price)
Uniswap V2 ввёл on-chain price oracle через накопление cumulative price:
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
TWAP за период = разница cumulativeLast / время периода. Это манипуляционно-устойчивый оракул: чтобы исказить TWAP за 1 час, нужно держать неверную цену весь час, что экономически нецелесообразно.
Uniswap V3 усовершенствовал систему: хранит ring buffer из 65535 наблюдений, tick-based accumulator вместо price accumulator — точнее при concentrated liquidity.
Защита от MEV и атак
Sandwich attacks
Классическая атака: MEV-бот видит pending своп, вставляет свою покупку до него (front-run) и продажу после (back-run), выжимая прибыль за счёт жертвы.
Защитные механизмы:
Slippage tolerance — пользователь задаёт максимальный acceptable price impact (обычно 0.5-1%). При превышении транзакция ревертируется.
Private mempool / Flashbots Protect — отправка транзакций в приватный мемпул, недоступный MEV-ботам. На уровне DEX-фронтенда можно использовать Flashbots rpc endpoint.
Commit-reveal схемы — пользователь сначала публикует хеш намерения, потом раскрывает параметры. Усложняет но не исключает MEV.
CoW Protocol / batch auctions — принципиальное решение: все ордера за период исполняются по uniform clearing price, front-running внутри batch невозможен.
Reentrancy protection
Пул использует nonReentrant mutex (паттерн lock в Uniswap):
uint private unlocked = 1;
modifier lock() {
require(unlocked == 1, "LOCKED");
unlocked = 0;
_;
unlocked = 1;
}
Оптимизация: использование одного storage slot вместо OpenZeppelin ReentrancyGuard — экономия ~2300 gas на каждом вызове.
Токеномика и управление протоколом
Protocol fee
Uniswap V2 ввёл механизм "protocol fee switch": помимо 0.3% LP fee, может включаться 0.05% protocol fee, идущий в treasury/governance. По умолчанию выключен — включается governance голосованием.
Для собственного AMM: рекомендуем изначально закладывать двухуровневую структуру комиссий (LP fee + protocol fee) даже если protocol fee = 0 при запуске. Добавлять его ретроспективно сложнее из-за необходимости апгрейда контрактов.
Governance токен
Паттерн: governance токен (GVN) распределяется через liquidity mining — LP-провайдеры стейкают свои LP-токены в Staking контракт и получают GVN пропорционально доле и времени.
// Упрощённая формула rewards
pendingReward = userLPBalance * (accRewardPerShare - userRewardDebt) / 1e12;
accRewardPerShare обновляется при каждом взаимодействии — это паттерн из MasterChef (SushiSwap), де-факто стандарт для liquidity mining.
Фронтенд и UX
Критические компоненты фронтенда для AMM DEX:
- Swap interface с real-time price impact и minimum received
- Liquidity management с визуализацией диапазона (для V3-style)
- Charts — интеграция с The Graph для исторических данных пула
- Wallet integration — wagmi + viem, поддержка WalletConnect v2
- Transaction simulation — tenderly simulate перед отправкой (предотвращает неожиданные ревёрты)
The Graph индексирует события пула (Swap, Mint, Burn) и предоставляет GraphQL API для аналитики. Альтернатива — собственный индексер на Ponder или Envio для полного контроля над данными.
Итоговый стек для production AMM DEX: Solidity + Foundry для контрактов, Hardhat для деплоя и тестов, React + wagmi для фронтенда, The Graph для индексации, Tenderly для мониторинга. Аудит обязателен — уязвимость в AMM пуле означает потерю всей TVL.







