Требуемые знания:
- 01-defi-ecosystem
AMM: Автоматический маркет-мейкер
Зачем это блокчейну?
Как обменять токены без посредника и ордер-бука? Автоматический маркет-мейкер (AMM) заменяет трейдеров математической формулой. Вместо “кто-то хочет продать по этой цене” AMM говорит: “вот формула, она определяет цену”.
Одна формула — xy = k — лежит в основе большинства DEX с общим TVL более $20 миллиардов. Uniswap, SushiSwap, PancakeSwap — все они работают на вариациях этой формулы. Понимание xy = k — это ключ к пониманию всего DeFi.
В этом уроке мы разберем формулу на трех уровнях: интуитивно (аналогия с качелями), алгоритмически (пошаговый расчет) и математически (формальное доказательство).
Интуитивное объяснение: кривая xy = k
Что такое AMM Pool?
AMM Pool — это смарт-контракт, который хранит два токена и позволяет обменивать один на другой. Ключевое правило: произведение количеств двух токенов должно оставаться постоянным.
- Token A — резерв x (например, ETH)
- Token B — резерв y (например, USDC)
- Инвариант: x * y = k (постоянное произведение)
- Цена A в терминах B: price_A = y / x
Аналогия с качелями
Представьте качели: когда одна сторона опускается (больше Token A добавлено в пул), другая поднимается (Token B становится дефицитнее и дороже). AMM всегда сохраняет баланс произведения.
Если в пуле 1000 ETH и 2,000,000 USDC, то k = 2,000,000,000. При добавлении ETH пул “отдает” USDC, но k не меняется.
Свойства кривой
Кривая xy = k — это гипербола. Ее ключевые свойства:
- Асимптотическая: Кривая никогда не касается осей. Резервы никогда не могут стать нулевыми
- Выпуклая: Большие свопы получают прогрессивно худшую цену (price impact)
- Симметричная: Обмен в обе стороны работает одинаково
- Бесконечная ликвидность: Пул всегда может выдать цену (пусть и плохую для больших ордеров)
Пошаговый расчет свопа
Разберем конкретный пример: обмен 10 ETH на USDC в пуле с 1000 ETH и 2,000,000 USDC.
Полный расчет
Пул: x = 1000 ETH, y = 2,000,000 USDC, k = 2,000,000,000.
Шаг 1: Trader отправляет dx = 10 ETH в пул.
Шаг 2: Вычитается комиссия 0.3%:
dx_effective = 10 * (1 - 0.003) = 10 * 0.997 = 9.97 ETH
fee = 10 * 0.003 = 0.03 ETH (остается в пуле)
Шаг 3: Рассчитываем новые резервы:
new_x = 1000 + 9.97 = 1009.97
new_y = k / new_x = 2,000,000,000 / 1009.97 = 1,980,258.49
dy = y - new_y = 2,000,000 - 1,980,258.49 = 19,741.51 USDC
Шаг 4: Trader получает 19,741.51 USDC.
Эффективная цена: 19,741.51 / 10 = 1,974.15 USDC/ETH (вместо спотовой 2,000).
Формула без комиссии
Выведем формулу аналитически.
Постановка
Имеем пул с резервами x и y. Инвариант: x * y = k. Trader вносит dx токенов A и получает dy токенов B.
Вывод
После свопа (без комиссии):
(x + dx) * (y - dy) = k
Раскрываем:
xy + dx*y - x*dy - dx*dy = k
Поскольку xy = k:
dx*y - x*dy - dx*dy = 0
dx*y = dy*(x + dx)
Решаем для dy:
Это формула выходного количества без комиссии. Заметьте: dy всегда меньше, чем dx * (y/x) — потому что знаменатель (x + dx) > x.
Формула с комиссией (0.3%)
В реальных AMM (Uniswap V2) комиссия вычитается до расчета:
Где f = 0.003 (0.3%).
Целочисленная арифметика
Solidity не поддерживает дробные числа. Uniswap V2 использует целочисленную формулу:
dy = y * dx * 997 / (x * 1000 + dx * 997)
Это эквивалентно:
(1 - f) = 997/1000
dy = y * (dx * 997) / (x * 1000 + dx * 997)
Пример с числами
Пул: x = 1000 ETH, y = 2,000,000 USDC. Swap: dx = 10 ETH.
dy = 2,000,000 * 10 * 997 / (1000 * 1000 + 10 * 997)
= 2,000,000 * 9,970 / (1,000,000 + 9,970)
= 19,940,000,000 / 1,009,970
= 19,742.50 USDC
Разница с нашим расчетом выше (19,741.51) — погрешность округления. В Solidity используется целочисленное деление (округление вниз).
Price Impact: влияние размера сделки
Price impact — это разница между спотовой ценой и эффективной ценой сделки. Чем больше сделка относительно пула, тем хуже цена.
Формула price impact
price_impact = dx / (x + dx)
Примеры
| Swap size | Pool size | Price Impact |
|---|---|---|
| 0.1 ETH | 1000 ETH | 0.01% |
| 1 ETH | 1000 ETH | 0.1% |
| 10 ETH | 1000 ETH | 0.99% |
| 100 ETH | 1000 ETH | 9.09% |
| 500 ETH | 1000 ETH | 33.33% |
Правило: Если ваш swap > 1% от пула, price impact будет заметным. Если > 5% — значительным.
Slippage Protection
Параметр amountOutMin защищает от проскальзывания:
function swap(uint amountIn, uint amountOutMin) external {
uint amountOut = getAmountOut(amountIn);
require(amountOut >= amountOutMin, "Slippage exceeded");
// ... выполнить своп
}
КРИТИЧНО: Установка
amountOutMin = 0— классическая уязвимость. Это позволяет sandwich attack: атакующий вставляет свою транзакцию до и после вашей, извлекая прибыль из вашего проскальзывания. Подробнее в Phase 7 (Security).
Комиссии и рост k
Комиссия 0.3% вычитается до расчета, но все токены остаются в пуле. Это означает, что после каждого свопа k растет:
Механизм
- Trader отправляет 10 ETH. Комиссия 0.03 ETH
- Расчет ведется для dx = 9.97 ETH (после комиссии)
- Но в пул добавляются все 10 ETH (включая комиссию)
- Из пула изымаются dy USDC (рассчитанных для 9.97 ETH)
- Результат: в пуле больше токенов, чем нужно для k
k_new = (x + 10) * (y - dy) > k_old
k_new > k_old, потому что 0.03 ETH комиссии остались в пуле.
Что это значит для LP?
Провайдеры ликвидности (LP) владеют долей пула через LP-токены. Когда k растет:
- Пул содержит больше активов
- Доля LP стоит больше
- LP зарабатывают на каждом свопе
Это основной incentive для предоставления ликвидности. Однако LP также несут риск impermanent loss (урок DEFI-05).
Алгоритмический уровень: псевдокод AMM
# Упрощенный AMM своп (Uniswap V2 логика)
def swap(token_in: address, amount_in: uint256, amount_out_min: uint256):
# 1. Определить направление
(reserve_in, reserve_out) = get_reserves(token_in)
# 2. Рассчитать output с комиссией 0.3%
amount_in_with_fee = amount_in * 997
numerator = reserve_out * amount_in_with_fee
denominator = reserve_in * 1000 + amount_in_with_fee
amount_out = numerator / denominator
# 3. Проверить slippage
require(amount_out >= amount_out_min, "Slippage exceeded")
# 4. Перевести токены
transfer(token_in, msg.sender -> pool, amount_in)
transfer(token_out, pool -> msg.sender, amount_out)
# 5. Обновить резервы
update_reserves()
# 6. Проверить k-инвариант (с комиссией k должен вырасти)
assert new_reserve_in * new_reserve_out >= old_k
Ключевые детали
- Порядок операций: Сначала перевод в пул, потом проверка инварианта
- Целочисленная арифметика: Нет дробных чисел, все через умножение/деление
- k-инвариант: Проверяется
>=, а не==, потому что комиссия увеличивает k
Математический уровень: формальный вывод
Константное произведение
Пусть — резервы пула, — инвариант.
Своп без комиссии
Trader вносит и получает :
Раскрываем:
Упрощаем (xy сокращается):
Своп с комиссией
Пусть (для 0.3%: ). Эффективный вход:
Маргинальная цена
Маргинальная цена — производная output по input:
При : (спотовая цена).
При : (бесконечный price impact).
Price impact
Это убывающая функция x (размера пула) и возрастающая функция (размера свопа). Чем глубже пул, тем меньше impact.
Рост k от комиссий
После свопа с комиссией:
Подставляем с комиссией:
Поскольку , имеем , следовательно:
Q.E.D. k монотонно растет с каждым свопом, когда .
Что дальше
В следующем уроке (DEFI-03) мы погрузимся в реальный код Uniswap V2:
- Смарт-контракты Factory и Pair
- Минтинг LP-токенов
- Flash Swaps
- Fork-тестирование на реальных данных Ethereum
В уроке DEFI-04 разберем революцию Uniswap V3 — концентрированная ликвидность, которая позволяет LP зарабатывать в 100-4000x больше комиссий на том же капитале. И заглянем в будущее: Uniswap V4 с hooks и singleton architecture.
Закончили урок?
Отметьте его как пройденный, чтобы отслеживать свой прогресс