Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 07.04 · 40 мин
Продвинутый
Uniswap V3Concentrated LiquidityTicksNFT PositionsUniswap V4Hooks
Требуемые знания:

Uniswap V3: Concentrated Liquidity

Зачем это блокчейну?

Uniswap V2 работает, но неэффективен: ликвидность размазана по всему ценовому диапазону от 0 до бесконечности. V3 позволяет LP выбирать конкретный ценовой диапазон — это может увеличить эффективность капитала в 4000+ раз.

Concentrated Liquidity — одна из самых значимых инноваций в DeFi. Она фундаментально изменила экономику предоставления ликвидности и стала стандартом для современных DEX.

Тики и ценовые диапазоны

Интуитивное объяснение

Представьте числовую ось с отметками (тиками). Каждый тик соответствует определенной цене. LP выбирает два тика: нижний и верхний — это его ценовой диапазон. Ликвидность активна только когда текущая цена находится внутри этого диапазона.

Uniswap V3: тики и ценовые диапазоны
$1635$2440$2000IN RANGE -- earning fees
tick_lower:74000
tick_upper:78000
price(tick_lower) = 1.0001^74000
$1635.38
price(tick_upper) = 1.0001^78000
$2439.65
Текущая цена (tick 76012)
$1999.84
Ширина диапазона
4,000 тиков
Формулаprice(i) = 1.0001^i. Каждый тик = 1 basis point (0.01%). Выход цены за диапазон превращает позицию в 100% одного токена.

Формула тика

Цена для каждого тика определяется формулой:

price(i)=1.0001iprice(i) = 1.0001^i

Каждый тик = 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

Обратная формула

Чтобы найти тик для заданной цены:

i=ln(price)ln(1.0001)i = \frac{\ln(price)}{\ln(1.0001)}

Tick Spacing

Не все тики доступны для создания позиций. Tick spacing зависит от комиссии пула:

Fee tierTick spacingМежду тиками
0.01%10.01%
0.05%100.1%
0.3%600.6%
1%2002%

LP может размещать ликвидность только на кратных tick spacing. Это оптимизация gas: меньше точек пересечения при свопе = дешевле.

Проверка знанийKnowledge check
Что представляет собой 'тик' (tick) в Uniswap V3 и какой шаг цены он определяет?
ОтветAnswer
Тик -- это дискретная точка на ценовой оси: price(i) = 1.0001^i. Каждый тик = 1 basis point = 0.01% изменения цены. LP выбирает нижний и верхний тик для своего ценового диапазона.

Виртуальные vs реальные резервы

Concentrated Liquidity: реальные vs виртуальные резервы
Token XToken Yp_ap_bV2 (full range)V3 (active)
V2 (full range): Ликвидность по всей кривой [0, infinity). Виртуальные резервы = реальные резервы.
V3 (concentrated): LP размещает ликвидность только между p_a и p_b. Реальных токенов нужно значительно меньше.
Out of range: Позиция неактивна, LP не зарабатывает fees. Конвертируется в 100% одного токена.
Виртуальные резервыV3 LP предоставляет реальные токены только для выбранного диапазона. Протокол рассчитывает виртуальные резервы так, чтобы в пределах [p_a, p_b] формула xy=k работала корректно.

Ключевая идея

В 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 токены

ХарактеристикаV2V3
Тип токенаERC-20ERC-721 (NFT)
ВзаимозаменяемостьДаНет
Диапазон[0, infinity)[tick_lower, tick_upper]
УправлениеNonfungiblePositionManagerNonfungiblePositionManager

Каждая V3 позиция уникальна: разный диапазон = разный NFT. Даже два LP с одинаковым диапазоном имеют разные NFT (разная ликвидность, разные earned fees).

NonfungiblePositionManager

Пользователи взаимодействуют с V3 через NonfungiblePositionManager:

  • mint() — создать позицию (выбрать диапазон, внести токены)
  • increaseLiquidity() — добавить ликвидность в существующую позицию
  • decreaseLiquidity() — убрать ликвидность
  • collect() — собрать накопленные комиссии
Проверка знанийKnowledge check
Почему LP-позиции в Uniswap V3 представлены как NFT (ERC-721), а не как взаимозаменяемые ERC-20 токены?
ОтветAnswer
Каждая V3 позиция имеет уникальный ценовой диапазон [tickLower, tickUpper], поэтому позиции не взаимозаменяемы. NFT хранит параметры конкретной позиции: тики, ликвидность, накопленные комиссии.

Капитальная эффективность

Расчет эффективности

Для диапазона [p_a, p_b] вокруг текущей цены P:

efficiency=PPpapbpbP\text{efficiency} = \frac{\sqrt{P}}{\sqrt{P} - \sqrt{p_a}} \cdot \frac{\sqrt{p_b}}{\sqrt{p_b} - \sqrt{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

Uniswap V4: singleton и hooks
Singleton Pattern
V2/V3: каждый пул -- отдельный контракт (Factory + create2). V4: ВСЕ пулы живут в одном контракте PoolManager. Создание пула = запись в mapping, не деплой контракта.
V2/V3
{ } { } { }
N контрактов
->
V4
{ }
1 контракт
Hooks: 10 точек кастомизации
beforeInitialize
afterInitialize
beforeAddLiquidity
afterAddLiquidity
beforeRemoveLiquidity
afterRemoveLiquidity
beforeSwap
afterSwap
beforeDonate
afterDonate
FeatureV2V3V4
Pool deployment
Отдельный контрактОтдельный контрактSingleton PoolManager
Creation cost
~4.5M gas~4.5M gas~99.99% дешевле
Multi-hop
Промежуточные переводыПромежуточные переводыFlash accounting
Кастомизация
НетНетHooks (10 точек)
Native ETH
Требует WETHТребует WETHНативная поддержка
V4 запущен 30 января 2025Singleton + hooks + flash accounting (EIP-1153 transient storage). V2 math и V3 concentrated liquidity -- фундаментальные концепции, V4 -- эволюция архитектуры.

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 пула, где можно вставить кастомную логику:

  1. beforeInitialize / afterInitialize — создание пула
  2. beforeAddLiquidity / afterAddLiquidity — добавление ликвидности
  3. beforeRemoveLiquidity / afterRemoveLiquidity — удаление ликвидности
  4. beforeSwap / afterSwap — свопы
  5. 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.

Проверка знанийKnowledge check
Почему permissions хука в V4 кодируются в адресе контракта, а не в storage?
ОтветAnswer
Кодирование в адресе делает permissions неизменяемыми и проверяемыми за O(1) без SLOAD: PoolManager на каждый callback просто проверяет младшие биты адреса hook. Если бит не выставлен -- callback пропускается. Хук нельзя 'обновить' и тихо включить новые callback'и -- для этого нужен новый адрес, что заметно для пользователей и аудиторов.

Математический уровень

Tick math

price(i)=1.0001iprice(i) = 1.0001^i i=ln(price)ln(1.0001)i = \left\lfloor \frac{\ln(price)}{\ln(1.0001)} \right\rfloor

Ликвидность V3

L=xyL = \sqrt{x \cdot y}

Хранится как uint128. Цена хранится как P\sqrt{P} в формате Q64.96 (fixed-point с 96 битами дробной части):

PQ64.96=P296\sqrt{P}_{Q64.96} = \sqrt{P} \cdot 2^{96}

Капитальная эффективность формально

Для обеспечения той же глубины в диапазоне [p_a, p_b], V3 требует:

capitalV3=capitalV2pbpa2papb\text{capital}_{V3} = \text{capital}_{V2} \cdot \frac{p_b - p_a}{2\sqrt{p_a \cdot p_b}}

При pa=1500,pb=2500p_a = 1500, p_b = 2500:

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

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 12