Uniswap V3: Concentrated Liquidity
Зачем это блокчейну?
Uniswap V2 работает, но неэффективен: ликвидность размазана по всему ценовому диапазону от 0 до бесконечности. V3 позволяет LP выбирать конкретный ценовой диапазон — это может увеличить эффективность капитала в 4000+ раз.
Concentrated Liquidity — одна из самых значимых инноваций в DeFi. Она фундаментально изменила экономику предоставления ликвидности и стала стандартом для современных DEX.
Тики и ценовые диапазоны
Интуитивное объяснение
Представьте числовую ось с отметками (тиками). Каждый тик соответствует определенной цене. LP выбирает два тика: нижний и верхний — это его ценовой диапазон. Ликвидность активна только когда текущая цена находится внутри этого диапазона.
Формула тика
Цена для каждого тика определяется формулой:
Каждый тик = 1 basis point = 0.01% изменения цены.
Примеры:
- Тик 0: price = 1.0001^0 = 1.0000
- Тик 1: price = 1.0001^1 = 1.0001 (+0.01%)
- Тик 100: price = 1.0001^100 = 1.01005 (+1%)
- Тик 76012: price = 1.0001^76012 ~ 2000 USDC/ETH
Обратная формула
Чтобы найти тик для заданной цены:
Tick Spacing
Не все тики доступны для создания позиций. Tick spacing зависит от комиссии пула:
| Fee tier | Tick spacing | Между тиками |
|---|---|---|
| 0.01% | 1 | 0.01% |
| 0.05% | 10 | 0.1% |
| 0.3% | 60 | 0.6% |
| 1% | 200 | 2% |
LP может размещать ликвидность только на кратных tick spacing. Это оптимизация gas: меньше точек пересечения при свопе = дешевле.
Виртуальные vs реальные резервы
Ключевая идея
В V2 формула xy = k использует реальные резервы. Чтобы обеспечить ликвидность для любой цены от 0 до бесконечности, нужно огромное количество токенов.
V3 переворачивает подход: LP предоставляет реальные токены только для диапазона [p_a, p_b]. Протокол рассчитывает виртуальные резервы так, чтобы внутри этого диапазона формула xy = k работала корректно.
Математика виртуальных резервов
Для позиции в диапазоне [p_a, p_b] с ликвидностью L:
Виртуальные резервы:
x_virtual = L / sqrt(P)
y_virtual = L * sqrt(P)
Реальные токены (только для диапазона):
x_real = L * (1/sqrt(P) - 1/sqrt(p_b))
y_real = L * (sqrt(P) - sqrt(p_a))
Где P — текущая цена.
Что происходит при выходе из диапазона?
- Цена уходит выше p_b: Позиция становится 100% Token A (дешевый токен). Все Token B проданы.
- Цена уходит ниже p_a: Позиция становится 100% Token B (дорогой токен). Все Token A проданы.
- В обоих случаях: LP не зарабатывает комиссии, пока цена не вернется в диапазон.
NFT позиции
V2 vs V3 LP токены
| Характеристика | V2 | V3 |
|---|---|---|
| Тип токена | ERC-20 | ERC-721 (NFT) |
| Взаимозаменяемость | Да | Нет |
| Диапазон | [0, infinity) | [tick_lower, tick_upper] |
| Управление | NonfungiblePositionManager | NonfungiblePositionManager |
Каждая V3 позиция уникальна: разный диапазон = разный NFT. Даже два LP с одинаковым диапазоном имеют разные NFT (разная ликвидность, разные earned fees).
NonfungiblePositionManager
Пользователи взаимодействуют с V3 через NonfungiblePositionManager:
mint()— создать позицию (выбрать диапазон, внести токены)increaseLiquidity()— добавить ликвидность в существующую позициюdecreaseLiquidity()— убрать ликвидностьcollect()— собрать накопленные комиссии
Капитальная эффективность
Расчет эффективности
Для диапазона [p_a, p_b] вокруг текущей цены P:
Пример: ETH/USDC, P = $2000, диапазон [1500, 2500]:
sqrt(2000) = 44.72
sqrt(1500) = 38.73
sqrt(2500) = 50.00
efficiency_x = 44.72 / (44.72 - 38.73) = 44.72 / 5.99 = 7.47
efficiency_y = 50.00 / (50.00 - 44.72) = 50.00 / 5.28 = 9.47
Итого: ~4.24x эффективнее V2 по среднему
Трейдофф
| Узкий диапазон | Широкий диапазон |
|---|---|
| Больше комиссий на единицу капитала | Меньше комиссий |
| Выше IL внутри диапазона | Ниже IL |
| Чаще выходит из диапазона | Редко выходит |
| Больше ребалансировок (gas) | Меньше управления |
| Для профессионалов | Для пассивных LP |
Алгоритмический уровень: V3 своп
V3 своп сложнее V2, потому что ликвидность разная на каждом тике:
# Упрощенный V3 своп (pseudocode)
def v3_swap(amount_in, zero_for_one):
remaining = amount_in
total_output = 0
while remaining > 0:
current_tick = get_current_tick()
liquidity = get_liquidity_at(current_tick)
# Рассчитать, сколько можно обменять в текущем тик-диапазоне
next_tick = get_next_initialized_tick(current_tick, zero_for_one)
(consumed, output) = compute_swap_step(
remaining, liquidity, current_tick, next_tick
)
remaining -= consumed
total_output += output
if consumed_reaches_next_tick:
# Пересекаем тик: активировать/деактивировать позиции
cross_tick(next_tick)
# Ликвидность на следующем тике может быть другой!
return total_output
Ключевое отличие от V2: В V2 один расчет по формуле. В V3 своп может пересечь несколько тиков, и на каждом тике ликвидность (L) может измениться.
Gas implications
Пересечение тика стоит gas (~20K-40K gas за тик). Большие свопы через много тиков — дороже. Это еще один трейдофф concentrated liquidity.
Uniswap V4: Singleton и Hooks
| Feature | V2 | V3 | V4 |
|---|---|---|---|
Pool deployment | Отдельный контракт | Отдельный контракт | Singleton PoolManager |
Creation cost | ~4.5M gas | ~4.5M gas | ~99.99% дешевле |
Multi-hop | Промежуточные переводы | Промежуточные переводы | Flash accounting |
Кастомизация | Нет | Нет | Hooks (10 точек) |
Native ETH | Требует WETH | Требует WETH | Нативная поддержка |
Singleton Pattern
V4, запущенный 30 января 2025, революционно меняет архитектуру:
- V2/V3: Каждый пул — отдельный контракт. Factory создает через
create2 - V4: ВСЕ пулы живут в одном контракте PoolManager (singleton)
- Создание пула = запись в mapping, не деплой контракта
- Gas на создание: ~99.99% дешевле
Flash Accounting (EIP-1153)
Multi-hop свопы в V2/V3 требуют промежуточных переводов токенов между пулами. V4 использует transient storage (EIP-1153) для flash accounting:
V2/V3: ETH -> Pool1 -> USDC -> Pool2 -> DAI (3 transfer)
V4: ETH -> PoolManager [internal accounting] -> DAI (1 transfer in, 1 transfer out)
Экономия gas на multi-hop: 30-50%.
Hooks
Hooks — 10 точек в lifecycle пула, где можно вставить кастомную логику:
beforeInitialize/afterInitialize— создание пулаbeforeAddLiquidity/afterAddLiquidity— добавление ликвидностиbeforeRemoveLiquidity/afterRemoveLiquidity— удаление ликвидностиbeforeSwap/afterSwap— свопыbeforeDonate/afterDonate— донаты в пул
Что можно сделать с hooks:
- Динамические комиссии (выше при волатильности)
- TWAP oracle (без отдельного контракта)
- Limit orders
- Автоматическая ребалансировка позиций
- KYC/compliance проверки
- MEV protection
Native ETH
V4 поддерживает нативный ETH без обертки в WETH. Это экономит ~21K gas на свопах с ETH.
Примечание: V4 в этом курсе — концептуальный материал. V2 math и V3 concentrated liquidity — фундаментальные концепции. V4 — эволюция архитектуры, а не формулы AMM.
V4 Hooks: production-паттерны (2025-2026)
V4 запущен в январе 2025. К середине 2025 разработчики создали более 2 500 hook-enabled пулов. Ниже — production-паттерны, которые формируют новую DEX-индустрию.
Lifecycle: 10 точек расширения
// 10 hook-флагов кодируются в адресе hook-контракта (3 младших бита x 4 пары + 2 bit для return delta)
contract MyHook is BaseHook {
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false, afterInitialize: true,
beforeAddLiquidity: true, afterAddLiquidity: false,
beforeRemoveLiquidity: true, afterRemoveLiquidity: false,
beforeSwap: true, afterSwap: true,
beforeDonate: false, afterDonate: false,
beforeSwapReturnDelta: true, // hook возвращает дельту -- меняет cashflow свопа
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
}
PoolManager проверяет адрес hook-контракта: каждый бит адреса соответствует разрешению. Если бит не выставлен — хук просто не вызовется. Это значит, что hook нельзя “подкрутить” после деплоя — адрес фиксирует поведение.
Pattern 1: TWAMM (Time-Weighted AMM)
Проблема: Большой ордер вызывает price impact и MEV-атаки. Решение: разбить его на множество маленьких свопов во времени.
Lifecycle TWAMM-хука:
1. User: place_order(size=1000 USDC, duration=1h)
2. beforeSwap (срабатывает на ЛЮБОМ свопе в пуле):
- hook рассчитывает, сколько USDC должен был торговаться с прошлого блока
- выполняет внутренний "виртуальный" своп через PoolManager
3. После -- обычный пользовательский своп
TWAMM-ордера всегда первая активность в блоке — это защищает от sandwich-атак. Реализован UF-командой как референс-хук.
Pattern 2: Dynamic Fees
Fee меняется в зависимости от волатильности (или иной метрики):
function beforeSwap(...) external returns (bytes4, BeforeSwapDelta, uint24 fee) {
uint24 dynamicFee = computeFee(volatility()); // 5 bps в стабильности, 100 bps в шторм
return (BaseHook.beforeSwap.selector, BeforeSwapDelta.wrap(0), dynamicFee | LPFeeLibrary.OVERRIDE_FEE_FLAG);
}
LP-провайдеры получают компенсацию за impermanent loss во время волатильности — их “цифровой spread” расширяется автоматически.
Pattern 3: JIT-mitigation
Just-In-Time liquidity — атака LP, который добавляет ликвидность непосредственно перед большим свопом, забирает fee и тут же выходит. Это вытесняет долгосрочных LP. Hook может:
beforeAddLiquidity: пометить позицию timestamp
beforeRemoveLiquidity: если age < threshold (например 10 блоков) -- штраф 50% fees в пользу пула
Pattern 4: MEV-rebate (Angstrom, SUClave)
Hook проводит аукцион на право быть first-to-trade. Победитель платит LP-провайдерам, а не sandwich-боту. Sorella Labs (Angstrom) реализует это как app-specific sequencer на уровне hook — LP получают 80%+ от MEV, который раньше уходил searchers.
Pattern 5: Savings vault + Brevis-attested hooks
LP-позиция превращается в yield-bearing vault: hook автоматически реинвестирует комиссии в lending-протокол (Aave/Morpho). Brevis-attested хуки используют ZK-доказательства событий с других цепочек: например, hook начинает динамически снижать fee, если Brevis подтверждает большой volume на конкурирующей DEX.
Singleton + Flash Accounting: gas-математика
V3 swap (через 2 пула):
ETH -> USDC: ERC20.transferFrom(user, Pool1) + ERC20.transfer(Pool1, Router)
USDC -> DAI: ERC20.transferFrom(Router, Pool2) + ERC20.transfer(Pool2, user)
= 4 storage SSTORE на ERC-20 балансах = ~80K gas
V4 swap (через PoolManager):
PoolManager.unlock() {
swap1: записать дельту в transient storage (TSTORE = 100 gas)
swap2: записать дельту в transient storage
settle(): один transferFrom от user, один transfer к user
}
= 2 ERC-20 трансфера + transient storage
~ -30...-50% gas на multi-hop
unlock — центральная семантика V4: между unlock() и моментом возврата управления PoolManager баланс пула может временно “не сходиться”, но к концу транзакции дельты должны равняться нулю (settle/take). Это и есть flash accounting.
Математический уровень
Tick math
Ликвидность V3
Хранится как uint128. Цена хранится как в формате Q64.96 (fixed-point с 96 битами дробной части):
Капитальная эффективность формально
Для обеспечения той же глубины в диапазоне [p_a, p_b], V3 требует:
При :
ratio = (2500 - 1500) / (2 * sqrt(1500 * 2500))
= 1000 / (2 * 1936.49)
= 1000 / 3872.98
= 0.258
V3 нужно 25.8% капитала V2 = эффективность 3.87x
Что дальше
В следующем уроке (DEFI-05) разберем Impermanent Loss — главный риск LP:
- Формула IL = 2*sqrt(r)/(1+r) - 1
- Пошаговый вывод формулы
- Когда LP прибыльно (fee APR vs IL)
- Управление позицией в V2 и V3